const request = require('supertest');
const express = require('express');

// Set env before loading the module
process.env.STRIPE_WEBHOOK_SECRET = 'whsec_test';

// Mock dependencies
jest.mock('../../../services/stripeWebhookService', () => ({
  constructEvent: jest.fn(),
  handleAccountUpdated: jest.fn(),
  handlePayoutPaid: jest.fn(),
  handlePayoutFailed: jest.fn(),
  handlePayoutCanceled: jest.fn(),
  handleAccountDeauthorized: jest.fn(),
}));

jest.mock('../../../services/disputeService', () => ({
  handleDisputeCreated: jest.fn(),
  handleDisputeClosed: jest.fn(),
}));

jest.mock('../../../utils/logger', () => ({
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
}));

const StripeWebhookService = require('../../../services/stripeWebhookService');
const DisputeService = require('../../../services/disputeService');
const stripeWebhooksRoutes = require('../../../routes/stripeWebhooks');

describe('Stripe Webhooks Routes', () => {
  let app;

  beforeEach(() => {
    app = express();
    // Add raw body middleware to capture raw body for signature verification
    app.use(express.raw({ type: 'application/json' }));
    app.use((req, res, next) => {
      req.rawBody = req.body;
      // Parse body for route handler
      if (Buffer.isBuffer(req.body)) {
        try {
          req.body = JSON.parse(req.body.toString());
        } catch (e) {
          req.body = {};
        }
      }
      next();
    });
    app.use('/stripe/webhooks', stripeWebhooksRoutes);
    jest.clearAllMocks();
  });

  describe('POST /stripe/webhooks', () => {
    it('should return 400 when signature is missing', async () => {
      const response = await request(app)
        .post('/stripe/webhooks')
        .send({ type: 'test' });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing signature');
    });

    it('should return 400 when signature verification fails', async () => {
      StripeWebhookService.constructEvent.mockImplementation(() => {
        throw new Error('Invalid signature');
      });

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'invalid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify({ type: 'test' }));

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Invalid signature');
    });

    it('should handle account.updated event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'account.updated',
        data: { object: { id: 'acct_123' } },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handleAccountUpdated.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(response.body.received).toBe(true);
      expect(response.body.eventId).toBe('evt_123');
      expect(StripeWebhookService.handleAccountUpdated).toHaveBeenCalledWith({ id: 'acct_123' });
    });

    it('should handle payout.paid event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'payout.paid',
        data: { object: { id: 'po_123' } },
        account: 'acct_456',
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handlePayoutPaid.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(StripeWebhookService.handlePayoutPaid).toHaveBeenCalledWith({ id: 'po_123' }, 'acct_456');
    });

    it('should handle payout.failed event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'payout.failed',
        data: { object: { id: 'po_123' } },
        account: 'acct_456',
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handlePayoutFailed.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(StripeWebhookService.handlePayoutFailed).toHaveBeenCalledWith({ id: 'po_123' }, 'acct_456');
    });

    it('should handle payout.canceled event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'payout.canceled',
        data: { object: { id: 'po_123' } },
        account: 'acct_456',
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handlePayoutCanceled.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(StripeWebhookService.handlePayoutCanceled).toHaveBeenCalledWith({ id: 'po_123' }, 'acct_456');
    });

    it('should handle account.application.deauthorized event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'account.application.deauthorized',
        data: { object: {} },
        account: 'acct_456',
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handleAccountDeauthorized.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(StripeWebhookService.handleAccountDeauthorized).toHaveBeenCalledWith('acct_456');
    });

    it('should handle charge.dispute.created event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'charge.dispute.created',
        data: { object: { id: 'dp_123' } },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      DisputeService.handleDisputeCreated.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(DisputeService.handleDisputeCreated).toHaveBeenCalledWith({ id: 'dp_123' });
    });

    it('should handle charge.dispute.closed event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'charge.dispute.closed',
        data: { object: { id: 'dp_123' } },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      DisputeService.handleDisputeClosed.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(DisputeService.handleDisputeClosed).toHaveBeenCalledWith({ id: 'dp_123' });
    });

    it('should handle charge.dispute.funds_reinstated event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'charge.dispute.funds_reinstated',
        data: { object: { id: 'dp_123' } },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      DisputeService.handleDisputeClosed.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(DisputeService.handleDisputeClosed).toHaveBeenCalled();
    });

    it('should handle charge.dispute.funds_withdrawn event', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'charge.dispute.funds_withdrawn',
        data: { object: { id: 'dp_123' } },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      DisputeService.handleDisputeClosed.mockResolvedValue();

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(DisputeService.handleDisputeClosed).toHaveBeenCalled();
    });

    it('should handle unhandled event types gracefully', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'customer.created',
        data: { object: {} },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      expect(response.status).toBe(200);
      expect(response.body.received).toBe(true);
    });

    it('should return 200 even when handler throws error', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'account.updated',
        data: { object: { id: 'acct_123' } },
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handleAccountUpdated.mockRejectedValue(new Error('Handler error'));

      const response = await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      // Should still return 200 to prevent Stripe retries
      expect(response.status).toBe(200);
      expect(response.body.received).toBe(true);
    });

    it('should log event with connected account when present', async () => {
      const mockEvent = {
        id: 'evt_123',
        type: 'payout.paid',
        data: { object: { id: 'po_123' } },
        account: 'acct_connected',
      };

      StripeWebhookService.constructEvent.mockReturnValue(mockEvent);
      StripeWebhookService.handlePayoutPaid.mockResolvedValue();

      await request(app)
        .post('/stripe/webhooks')
        .set('stripe-signature', 'valid-signature')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(mockEvent));

      // Logger should have been called with connected account info
      const logger = require('../../../utils/logger');
      expect(logger.info).toHaveBeenCalledWith(
        'Stripe webhook received',
        expect.objectContaining({
          eventId: 'evt_123',
          eventType: 'payout.paid',
          connectedAccount: 'acct_connected',
        })
      );
    });
  });
});
