import 'reflect-metadata';
import { type CanActivate, type ExecutionContext, type INestApplication } from '@nestjs/common';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { AuthGuard } from '../auth/auth.guard.js';
import { TeamsController } from './teams.controller.js';
import { TeamsService } from './teams.service.js';

const teamAlpha = { id: 'team-alpha', name: 'Alpha' };
const teamBeta = { id: 'team-beta', name: 'Beta' };

// user-1 is a member of team-alpha only; admin-1 has role admin.
let currentUser: { id: string; role?: string } = { id: 'user-1' };

const teamsServiceMock = {
  findAll: vi.fn(() => Promise.resolve([teamAlpha, teamBeta])),
  findAllForUser: vi.fn((userId: string) =>
    Promise.resolve(userId === 'user-1' ? [teamAlpha] : []),
  ),
  findById: vi.fn((id: string) => Promise.resolve([teamAlpha, teamBeta].find((t) => t.id === id))),
  listMembers: vi.fn(() => Promise.resolve([{ teamId: 'team-alpha', userId: 'user-1' }])),
  isMember: vi.fn((teamId: string, userId: string) =>
    Promise.resolve(teamId === 'team-alpha' && userId === 'user-1'),
  ),
};

const authGuard: CanActivate = {
  canActivate(context: ExecutionContext): boolean {
    const requestContext = context
      .switchToHttp()
      .getRequest<{ user?: { id: string; role?: string } }>();
    requestContext.user = currentUser;
    return true;
  },
};

describe('teams endpoints are scoped to membership', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [TeamsController],
      providers: [{ provide: TeamsService, useValue: teamsServiceMock }],
    })
      .overrideGuard(AuthGuard)
      .useValue(authGuard)
      .compile();

    app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
    await app.init();
    await app.getHttpAdapter().getInstance().ready();
  });

  beforeEach(() => {
    currentUser = { id: 'user-1' };
    vi.clearAllMocks();
  });

  afterAll(async () => {
    await app.close();
  });

  it('GET /api/teams returns only the teams the user belongs to', async () => {
    const response = await request(app.getHttpServer()).get('/api/teams');
    expect(response.status).toBe(200);
    expect(response.body).toEqual([teamAlpha]);
    expect(teamsServiceMock.findAll).not.toHaveBeenCalled();
  });

  it('GET /api/teams returns every team for an admin', async () => {
    currentUser = { id: 'admin-1', role: 'admin' };
    const response = await request(app.getHttpServer()).get('/api/teams');
    expect(response.status).toBe(200);
    expect(response.body).toEqual([teamAlpha, teamBeta]);
    expect(teamsServiceMock.findAllForUser).not.toHaveBeenCalled();
  });

  it('GET /api/teams/:teamId returns 403 for a non-member', async () => {
    const response = await request(app.getHttpServer()).get('/api/teams/team-beta');
    expect(response.status).toBe(403);
  });

  it('GET /api/teams/:teamId returns 404 for a missing team', async () => {
    const response = await request(app.getHttpServer()).get('/api/teams/team-missing');
    expect(response.status).toBe(404);
  });

  it('GET /api/teams/:teamId returns the team for a member', async () => {
    const response = await request(app.getHttpServer()).get('/api/teams/team-alpha');
    expect(response.status).toBe(200);
    expect(response.body).toEqual(teamAlpha);
  });

  it('GET /api/teams/:teamId/members returns 403 for a non-member and members for a member', async () => {
    const denied = await request(app.getHttpServer()).get('/api/teams/team-beta/members');
    expect(denied.status).toBe(403);
    expect(teamsServiceMock.listMembers).not.toHaveBeenCalled();

    const allowed = await request(app.getHttpServer()).get('/api/teams/team-alpha/members');
    expect(allowed.status).toBe(200);
    expect(allowed.body).toEqual([{ teamId: 'team-alpha', userId: 'user-1' }]);
  });

  it('GET /api/teams/:teamId/members/:userId allows a self-lookup on any team', async () => {
    const response = await request(app.getHttpServer()).get('/api/teams/team-beta/members/user-1');
    expect(response.status).toBe(200);
    expect(response.body).toEqual({ isMember: false });
  });

  it('GET /api/teams/:teamId/members/:userId denies looking up another user on a foreign team', async () => {
    const response = await request(app.getHttpServer()).get('/api/teams/team-beta/members/user-2');
    expect(response.status).toBe(403);
  });

  it('an admin can look up any membership', async () => {
    currentUser = { id: 'admin-1', role: 'admin' };
    const response = await request(app.getHttpServer()).get('/api/teams/team-alpha/members/user-1');
    expect(response.status).toBe(200);
    expect(response.body).toEqual({ isMember: true });
  });
});
