import 'reflect-metadata';

import { Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import request from 'supertest';
import { vi } from 'vitest';
import { TeamController } from './TeamController';
import { TeamService } from './TeamService';
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
import { UpdateTeamInput } from './dtos/TeamDto';
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('TeamController', () => {
  let controller: TeamController;
  let service: ReturnType<typeof vi.mocked<TeamService>>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [TeamController],
      providers: [
        {
          provide: TeamService,
          useValue: {
            getAll: vi.fn(),
            getDetails: vi.fn(),
            getMembers: vi.fn(),
            getJoinRequests: vi.fn(),
            create: vi.fn(),
            update: vi.fn(),
            getDriverTeam: vi.fn(),
            getMembership: vi.fn(),
          },
        },
      ],
    }).compile();

    controller = module.get<TeamController>(TeamController);
    service = vi.mocked(module.get(TeamService));
  });

  describe('getAll', () => {
    it('should return all teams', async () => {
      const result = { teams: [], totalCount: 0 };
      service.getAll.mockResolvedValue(result);

      const response = await controller.getAll();

      expect(service.getAll).toHaveBeenCalled();
      expect(response).toEqual(result);
    });
  });

  describe('getDetails', () => {
    it('should return team details', async () => {
      const teamId = 'team-123';
      const userId = 'user-456';
      const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false };
      service.getDetails.mockResolvedValue(result);

      const mockReq = { user: { userId } } as any;

      const response = await controller.getDetails(teamId, mockReq);

      expect(service.getDetails).toHaveBeenCalledWith(teamId, userId);
      expect(response).toEqual(result);
    });
  });

  describe('getMembers', () => {
    it('should return team members', async () => {
      const teamId = 'team-123';
      const result = { members: [], totalCount: 0, ownerCount: 0, managerCount: 0, memberCount: 0 };
      service.getMembers.mockResolvedValue(result);

      const response = await controller.getMembers(teamId);

      expect(service.getMembers).toHaveBeenCalledWith(teamId);
      expect(response).toEqual(result);
    });
  });

  describe('getJoinRequests', () => {
    it('should return join requests', async () => {
      const teamId = 'team-123';
      const result = { requests: [], pendingCount: 0, totalCount: 0 };
      service.getJoinRequests.mockResolvedValue(result);

      const response = await controller.getJoinRequests(teamId);

      expect(service.getJoinRequests).toHaveBeenCalledWith(teamId);
      expect(response).toEqual(result);
    });
  });

  describe('create', () => {
    it('should create team', async () => {
      const input: CreateTeamInputDTO = { name: 'New Team', tag: 'TAG' };
      const userId = 'user-123';
      const result = { id: 'team-456', success: true };
      service.create.mockResolvedValue(result);

      const mockReq = { user: { userId } } as any;

      const response = await controller.create(input, mockReq);

      expect(service.create).toHaveBeenCalledWith(input, userId);
      expect(response).toEqual(result);
    });
  });

  describe('update', () => {
    it('should update team', async () => {
      const teamId = 'team-123';
      const userId = 'user-456';
      const input: UpdateTeamInput = { name: 'Updated Team', updatedBy: userId };
      const result = { success: true };
      service.update.mockResolvedValue(result);

      const mockReq = { user: { userId } } as any;

      const response = await controller.update(teamId, input, mockReq);

      expect(service.update).toHaveBeenCalledWith(teamId, input, userId);
      expect(response).toEqual(result);
    });
  });

  describe('getDriverTeam', () => {
    it('should return driver team', async () => {
      const driverId = 'driver-123';
      const result = { team: { id: 'team-456', name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: { role: 'member' as const, joinedAt: '2023-01-01', isActive: true }, isOwner: false, canManage: false };
      service.getDriverTeam.mockResolvedValue(result);

      const response = await controller.getDriverTeam(driverId);

      expect(service.getDriverTeam).toHaveBeenCalledWith(driverId);
      expect(response).toEqual(result);
    });
  });

  describe('getMembership', () => {
    it('should return team membership', async () => {
      const teamId = 'team-123';
      const driverId = 'driver-456';
      const result = { role: 'member' as const, joinedAt: '2023-01-01', isActive: true };
      service.getMembership.mockResolvedValue(result);

      const response = await controller.getMembership(teamId, driverId);

      expect(service.getMembership).toHaveBeenCalledWith(teamId, driverId);
      expect(response).toEqual(result);
    });
  });

  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: {},
        loadedFrom: 'defaults',
        loadedAtIso: new Date(0).toISOString(),
      })),
    };

    beforeEach(async () => {
      const module = await Test.createTestingModule({
        controllers: [TeamController],
        providers: [
          {
            provide: TeamService,
            useValue: {
              getAll: vi.fn(async () => ({ teams: [], totalCount: 0 })),
              getJoinRequests: vi.fn(async () => ({ requests: [], pendingCount: 0, totalCount: 0 })),
            },
          },
        ],
      }).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('/teams/all').expect(200);
    });

    it('denies non-public endpoint by default when not authenticated (401)', async () => {
      await request(app.getHttpServer()).get('/teams/t1/join-requests').expect(401);
    });

    it('allows non-public endpoint when authenticated via session port', async () => {
      vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
        token: 't',
        user: { id: 'user-1' },
      });

      await request(app.getHttpServer()).get('/teams/t1/join-requests').expect(200);
    });
  });
});