import { ForbiddenException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { LaravelIdentityService } from '../common/services/laravel-identity.service';
import { ServiceCredentialGuard } from '../common/guards/service-credential/service-credential.guard';
import { AtlasProxyController } from './atlas-proxy.controller';
import { AtlasProxyService } from './atlas-proxy.service';

describe('AtlasProxyController', () => {
  let controller: AtlasProxyController;
  let proxyService: AtlasProxyService;
  let identity: jest.Mocked<LaravelIdentityService>;

  beforeEach(async () => {
    const mockService = {
      forwardRequest: jest.fn(),
    };

    const mockIdentity = {
      resolveMe: jest.fn(),
      canUserAccessClient: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [AtlasProxyController],
      providers: [
        {
          provide: AtlasProxyService,
          useValue: mockService,
        },
        {
          provide: LaravelIdentityService,
          useValue: mockIdentity,
        },
      ],
    })
      .overrideGuard(ServiceCredentialGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get(AtlasProxyController);
    proxyService = module.get(AtlasProxyService);
    identity = module.get(LaravelIdentityService);
  });

  describe('getPlatformVideos', () => {
    it('should forward GET /platform-videos to AtlasProxyService', async () => {
      const mockResponse = { videos: [] };
      jest
        .spyOn(proxyService, 'forwardRequest')
        .mockResolvedValueOnce(mockResponse);

      const result = await controller.getPlatformVideos();

      expect(proxyService.forwardRequest).toHaveBeenCalledWith(
        'GET',
        '/platform-videos',
      );
      expect(result).toEqual(mockResponse);
    });
  });

  describe('updateClient', () => {
    it('should forward PUT /clients/:stripeId to AtlasProxyService when owner matches', async () => {
      const stripeId = 'stripe_123';
      const body = { name: 'Updated Client' };
      const mockResponse = { id: stripeId, name: 'Updated Client' };

      identity.resolveMe.mockResolvedValue({ uuid: 'u1', stripe_id: stripeId });
      jest
        .spyOn(proxyService, 'forwardRequest')
        .mockResolvedValueOnce(mockResponse);

      const req = { user: { type: 'user', rawToken: 'tok' } } as any;
      const result = await controller.updateClient(stripeId, body, req);

      expect(proxyService.forwardRequest).toHaveBeenCalledWith(
        'PUT',
        `/clients/${encodeURIComponent(stripeId)}`,
        body,
      );
      expect(result).toEqual(mockResponse);
    });
  });

  describe('onboardingBusiness', () => {
    it('should forward POST /clients/onboarding-business to AtlasProxyService', async () => {
      const body = { businessName: 'My Business' };
      const mockResponse = { id: 'client_123', businessName: 'My Business' };

      identity.resolveMe.mockResolvedValue({
        uuid: 'u1',
        stripe_id: 'cus_123',
      });
      jest
        .spyOn(proxyService, 'forwardRequest')
        .mockResolvedValueOnce(mockResponse);

      const req = { user: { type: 'user', rawToken: 'tok' } } as any;
      const result = await controller.onboardingBusiness(body, req);

      expect(proxyService.forwardRequest).toHaveBeenCalledWith(
        'POST',
        '/clients/onboarding-business',
        expect.objectContaining({
          businessName: 'My Business',
          stripeId: 'cus_123',
          customerApiUuid: 'u1',
        }),
      );
      expect(result).toEqual(mockResponse);
    });
  });

  describe('ownership', () => {
    it('rejects PUT clients/:stripeId when the token does not own that stripeId', async () => {
      identity.resolveMe.mockResolvedValue({
        uuid: 'u1',
        stripe_id: 'cus_OWNER',
      });
      const req = { user: { type: 'user', rawToken: 'tok' } } as any;
      await expect(
        controller.updateClient('cus_VICTIM', { name: 'x' }, req),
      ).rejects.toThrow(ForbiddenException);
      expect(proxyService.forwardRequest).not.toHaveBeenCalled();
    });

    it('forwards PUT when stripeId matches the token owner, with encoded path', async () => {
      identity.resolveMe.mockResolvedValue({
        uuid: 'u1',
        stripe_id: 'cus_OWNER',
      });
      const req = { user: { type: 'user', rawToken: 'tok' } } as any;
      await controller.updateClient('cus_OWNER', { name: 'x' }, req);
      expect(proxyService.forwardRequest).toHaveBeenCalledWith(
        'PUT',
        '/clients/cus_OWNER',
        { name: 'x' },
      );
    });

    it('onboarding-business stamps stripeId/customerApiUuid from the resolved identity', async () => {
      identity.resolveMe.mockResolvedValue({
        uuid: 'u1',
        stripe_id: 'cus_OWNER',
      });
      const req = { user: { type: 'user', rawToken: 'tok' } } as any;
      await controller.onboardingBusiness(
        { stripeId: 'cus_FAKE', email: 'a@b.c' },
        req,
      );
      expect(proxyService.forwardRequest).toHaveBeenCalledWith(
        'POST',
        '/clients/onboarding-business',
        expect.objectContaining({
          stripeId: 'cus_OWNER',
          customerApiUuid: 'u1',
        }),
      );
    });

    it('rejects when /me cannot be resolved (fail closed)', async () => {
      identity.resolveMe.mockResolvedValue(null);
      const req = { user: { type: 'user', rawToken: 'tok' } } as any;
      await expect(controller.updateClient('cus_X', {}, req)).rejects.toThrow(
        ForbiddenException,
      );
    });
  });
});
