import 'reflect-metadata';

import { Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import request from 'supertest';
import { vi } from 'vitest';
import { SponsorController } from './SponsorController';
import { SponsorService } from './SponsorService';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import type { AuthorizationService } from '../auth/AuthorizationService';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
import type { PolicyService, PolicySnapshot } from '../policy/PolicyService';

describe('SponsorController', () => {
  let controller: SponsorController;
  let sponsorService: ReturnType<typeof vi.mocked<SponsorService>>;

  beforeEach(async () => {
    const moduleBuilder = Test.createTestingModule({
      controllers: [SponsorController],
      providers: [
        {
          provide: SponsorService,
          useValue: {
            getEntitySponsorshipPricing: vi.fn(),
            getSponsors: vi.fn(),
            createSponsor: vi.fn(),
            getSponsorDashboard: vi.fn(),
            getSponsorSponsorships: vi.fn(),
            getSponsor: vi.fn(),
            getPendingSponsorshipRequests: vi.fn(),
            acceptSponsorshipRequest: vi.fn(),
            rejectSponsorshipRequest: vi.fn(),
            getSponsorBilling: vi.fn(),
            getAvailableLeagues: vi.fn(),
            getLeagueDetail: vi.fn(),
            getSponsorSettings: vi.fn(),
            updateSponsorSettings: vi.fn(),
          },
        },
      ],
    })
      .overrideGuard(AuthenticationGuard)
      .useValue({ canActivate: vi.fn().mockResolvedValue(true) })
      .overrideGuard(AuthorizationGuard)
      .useValue({ canActivate: vi.fn().mockResolvedValue(true) })
      .overrideGuard(FeatureAvailabilityGuard)
      .useValue({ canActivate: vi.fn().mockResolvedValue(true) });

    const module: TestingModule = await moduleBuilder.compile();

    controller = module.get<SponsorController>(SponsorController);
    sponsorService = vi.mocked(module.get(SponsorService));
  });

  describe('getEntitySponsorshipPricing', () => {
    it('should return sponsorship pricing', async () => {
      const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] };
      sponsorService.getEntitySponsorshipPricing.mockResolvedValue(mockResult as any);

      const result = await controller.getEntitySponsorshipPricing();

      expect(result).toEqual(mockResult);
      expect(sponsorService.getEntitySponsorshipPricing).toHaveBeenCalled();
    });
  });

  describe('getSponsors', () => {
    it('should return sponsors list', async () => {
      const mockResult = { sponsors: [] };
      sponsorService.getSponsors.mockResolvedValue(mockResult as any);

      const result = await controller.getSponsors();

      expect(result).toEqual(mockResult);
      expect(sponsorService.getSponsors).toHaveBeenCalled();
    });
  });

  describe('createSponsor', () => {
    it('should create sponsor', async () => {
      const input = { name: 'Test Sponsor', contactEmail: 'test@example.com' };
      const mockResult = { sponsor: { id: 's1', name: 'Test Sponsor' } };
      sponsorService.createSponsor.mockResolvedValue(mockResult as any);

      const result = await controller.createSponsor(input as any);

      expect(result).toEqual(mockResult);
      expect(sponsorService.createSponsor).toHaveBeenCalledWith(input);
    });
  });

  describe('getSponsorDashboard', () => {
    it('should return sponsor dashboard', async () => {
      const sponsorId = 's1';
      const mockResult = { sponsorId, metrics: {} as any, sponsoredLeagues: [], investment: {} as any };
      sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as any);

      const result = await controller.getSponsorDashboard(sponsorId);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getSponsorDashboard).toHaveBeenCalledWith({ sponsorId });
    });

    it('should throw when sponsor not found', async () => {
      const sponsorId = 's1';
      sponsorService.getSponsorDashboard.mockRejectedValue(new Error('Sponsor dashboard not found'));

      await expect(controller.getSponsorDashboard(sponsorId)).rejects.toBeInstanceOf(Error);
    });
  });

  describe('getSponsorSponsorships', () => {
    it('should return sponsor sponsorships', async () => {
      const sponsorId = 's1';
      const mockResult = {
        sponsorId,
        sponsorName: 'S1',
        sponsorships: [],
        summary: {
          totalSponsorships: 0,
          activeSponsorships: 0,
          totalInvestment: 0,
          totalPlatformFees: 0,
          currency: 'USD',
        },
      };
      sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as any);

      const result = await controller.getSponsorSponsorships(sponsorId);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getSponsorSponsorships).toHaveBeenCalledWith({ sponsorId });
    });

    it('should throw when sponsor not found', async () => {
      const sponsorId = 's1';
      sponsorService.getSponsorSponsorships.mockRejectedValue(new Error('Sponsor sponsorships not found'));

      await expect(controller.getSponsorSponsorships(sponsorId)).rejects.toBeInstanceOf(Error);
    });
  });

  describe('getSponsor', () => {
    it('should return sponsor', async () => {
      const sponsorId = 's1';
      const mockResult = { sponsor: { id: sponsorId, name: 'S1' } };
      sponsorService.getSponsor.mockResolvedValue(mockResult as any);

      const result = await controller.getSponsor(sponsorId);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getSponsor).toHaveBeenCalledWith(sponsorId);
    });

    it('should throw when sponsor not found', async () => {
      const sponsorId = 's1';
      sponsorService.getSponsor.mockRejectedValue(new Error('Sponsor not found'));

      await expect(controller.getSponsor(sponsorId)).rejects.toBeInstanceOf(Error);
    });
  });

  describe('getPendingSponsorshipRequests', () => {
    it('should return pending sponsorship requests', async () => {
      const query = { entityType: 'season' as const, entityId: 'season-1' };
      const mockResult = {
        entityType: 'season',
        entityId: 'season-1',
        requests: [],
        totalCount: 0,
      };
      sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as any);

      const result = await controller.getPendingSponsorshipRequests(query);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getPendingSponsorshipRequests).toHaveBeenCalledWith(query);
    });
  });

  describe('acceptSponsorshipRequest', () => {
    it('should accept sponsorship request', async () => {
      const requestId = 'r1';
      const input = { respondedBy: 'u1' };
      const mockResult = {
        requestId,
        sponsorshipId: 'sp1',
        status: 'accepted' as const,
        acceptedAt: new Date(),
        platformFee: 10,
        netAmount: 90,
      };
      sponsorService.acceptSponsorshipRequest.mockResolvedValue(mockResult as any);

      const result = await controller.acceptSponsorshipRequest(requestId, input as any);

      expect(result).toEqual(mockResult);
      expect(sponsorService.acceptSponsorshipRequest).toHaveBeenCalledWith(
        requestId,
        input.respondedBy,
      );
    });

    it('should throw on error', async () => {
      const requestId = 'r1';
      const input = { respondedBy: 'u1' };
      sponsorService.acceptSponsorshipRequest.mockRejectedValue(new Error('Accept sponsorship request failed'));

      await expect(controller.acceptSponsorshipRequest(requestId, input as any)).rejects.toBeInstanceOf(Error);
    });
  });

  describe('rejectSponsorshipRequest', () => {
    it('should reject sponsorship request', async () => {
      const requestId = 'r1';
      const input = { respondedBy: 'u1', reason: 'Not interested' };
      const mockResult = {
        requestId,
        status: 'rejected' as const,
        rejectedAt: new Date(),
        reason: 'Not interested',
      };
      sponsorService.rejectSponsorshipRequest.mockResolvedValue(mockResult as any);

      const result = await controller.rejectSponsorshipRequest(requestId, input as any);

      expect(result).toEqual(mockResult);
      expect(sponsorService.rejectSponsorshipRequest).toHaveBeenCalledWith(
        requestId,
        input.respondedBy,
        input.reason,
      );
    });

    it('should throw on error', async () => {
      const requestId = 'r1';
      const input = { respondedBy: 'u1' };
      sponsorService.rejectSponsorshipRequest.mockRejectedValue(new Error('Reject sponsorship request failed'));

      await expect(controller.rejectSponsorshipRequest(requestId, input as any)).rejects.toBeInstanceOf(Error);
    });
  });

  describe('getSponsorBilling', () => {
    it('should return sponsor billing', async () => {
      const sponsorId = 's1';
      const mockResult = {
        paymentMethods: [],
        invoices: [],
        stats: {
          totalSpent: 0,
          pendingAmount: 0,
          nextPaymentDate: '2025-01-01',
          nextPaymentAmount: 0,
          activeSponsorships: 0,
          averageMonthlySpend: 0,
        },
      };
      sponsorService.getSponsorBilling.mockResolvedValue(mockResult as any);

      const result = await controller.getSponsorBilling(sponsorId);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getSponsorBilling).toHaveBeenCalledWith(sponsorId);
    });
  });

  describe('getAvailableLeagues', () => {
    it('should return available leagues', async () => {
      const mockResult: any[] = [];
      sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as any);

      const result = await controller.getAvailableLeagues();

      expect(result).toEqual(mockResult);
      expect(sponsorService.getAvailableLeagues).toHaveBeenCalled();
    });
  });

  describe('getLeagueDetail', () => {
    it('should return league detail', async () => {
      const leagueId = 'league-1';
      const mockResult = {
        league: { id: leagueId } as any,
        drivers: [],
        races: [],
      };
      sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as any);

      const result = await controller.getLeagueDetail(leagueId);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getLeagueDetail).toHaveBeenCalledWith(leagueId);
    });
  });

  describe('getSponsorSettings', () => {
    it('should return sponsor settings', async () => {
      const sponsorId = 's1';
      const mockResult = {
        profile: {} as any,
        notifications: {} as any,
        privacy: {} as any,
      };
      sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any);

      const result = await controller.getSponsorSettings(sponsorId);

      expect(result).toEqual(mockResult);
      expect(sponsorService.getSponsorSettings).toHaveBeenCalledWith(sponsorId);
    });
  });

  describe('updateSponsorSettings', () => {
    it('should update sponsor settings', async () => {
      const sponsorId = 's1';
      const input = {};
      const mockResult = { success: true };
      sponsorService.updateSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any);

      const result = await controller.updateSponsorSettings(sponsorId, input);

      expect(result).toEqual(mockResult);
      expect(sponsorService.updateSponsorSettings).toHaveBeenCalledWith(sponsorId, input);
    });
  });

  describe('auth guards (HTTP)', () => {
    let app: any;

    const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
      getCurrentSession: vi.fn(async () => null),
    };

    const authorizationService: Pick<AuthorizationService, 'getRolesForUser'> = {
      getRolesForUser: vi.fn(() => []),
    };

    const policyService: Pick<PolicyService, 'getSnapshot'> = {
      getSnapshot: vi.fn(async (): Promise<PolicySnapshot> => ({
        policyVersion: 1,
        operationalMode: 'normal',
        maintenanceAllowlist: { view: [], mutate: [] },
        capabilities: { 'sponsors.portal': 'enabled' },
        loadedFrom: 'defaults',
        loadedAtIso: new Date(0).toISOString(),
      })),
    };

    beforeEach(async () => {
      const module = await Test.createTestingModule({
        controllers: [SponsorController],
        providers: [
          {
            provide: SponsorService,
            useValue: {
              getEntitySponsorshipPricing: vi.fn(async () => ({ entityType: 'season', entityId: 's1', pricing: [] })),
              getSponsors: vi.fn(async () => ({ sponsors: [] })),
            },
          },
        ],
      }).compile();

      app = module.createNestApplication();

      const reflector = new Reflector();
      app.useGlobalGuards(
        new AuthenticationGuard(sessionPort as any),
        new AuthorizationGuard(reflector, authorizationService as any),
        new FeatureAvailabilityGuard(reflector, policyService as any),
      );

      await app.init();
    });

    afterEach(async () => {
      await app?.close();
      vi.clearAllMocks();
    });

    it('allows @Public() endpoint without a session', async () => {
      await request(app.getHttpServer()).get('/sponsors/pricing').expect(200);
    });

    it('denies protected endpoint when not authenticated (401)', async () => {
      await request(app.getHttpServer()).get('/sponsors').expect(401);
    });

    it('returns 403 when authenticated but missing required role', async () => {
      vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
        token: 't',
        user: { id: 'user-1' },
      });
      vi.mocked(authorizationService.getRolesForUser).mockReturnValueOnce(['user']);

      await request(app.getHttpServer()).get('/sponsors').expect(403);
    });

    it('returns 404 when role is satisfied but capability is disabled', async () => {
      vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
        token: 't',
        user: { id: 'user-1' },
      });
      vi.mocked(authorizationService.getRolesForUser).mockReturnValueOnce(['admin']);
      vi.mocked(policyService.getSnapshot).mockResolvedValueOnce({
        policyVersion: 1,
        operationalMode: 'normal',
        maintenanceAllowlist: { view: [], mutate: [] },
        capabilities: { 'sponsors.portal': 'disabled' },
        loadedFrom: 'defaults',
        loadedAtIso: new Date(0).toISOString(),
      });

      await request(app.getHttpServer()).get('/sponsors').expect(404);
    });

    it('allows access when role is satisfied and capability is enabled', async () => {
      vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
        token: 't',
        user: { id: 'user-1' },
      });
      vi.mocked(authorizationService.getRolesForUser).mockReturnValueOnce(['admin']);
      vi.mocked(policyService.getSnapshot).mockResolvedValueOnce({
        policyVersion: 1,
        operationalMode: 'normal',
        maintenanceAllowlist: { view: [], mutate: [] },
        capabilities: { 'sponsors.portal': 'enabled' },
        loadedFrom: 'defaults',
        loadedAtIso: new Date(0).toISOString(),
      });

      await request(app.getHttpServer()).get('/sponsors').expect(200);
    });
  });
});
