import 'reflect-metadata';

import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';

describe('League season schedule publish/unpublish (HTTP, season-scoped)', () => {
  const originalEnv = { ...process.env };

  let app: any;

  beforeAll(async () => {
    vi.resetModules();

    process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
    process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
    delete process.env.DATABASE_URL;

    const { AppModule } = await import('../../app.module');

    const module = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = module.createNestApplication();

    // Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
    app.use(requestContextMiddleware);

    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );

    const reflector = new Reflector();
    const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);

    const authorizationService = {
      getRolesForUser: () => [],
    };

    const policyService = {
      getSnapshot: async () => ({
        policyVersion: 1,
        operationalMode: 'normal',
        maintenanceAllowlist: { view: [], mutate: [] },
        capabilities: {},
        loadedFrom: 'defaults',
        loadedAtIso: new Date(0).toISOString(),
      }),
    };

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

    await app.init();
  }, 20_000);

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

    process.env = originalEnv;
    vi.restoreAllMocks();
  });

  it('rejects unauthenticated actor (401)', async () => {
    await request(app.getHttpServer())
      .post('/leagues/league-5/seasons/season-1/schedule/publish')
      .send({})
      .expect(401);

    await request(app.getHttpServer())
      .post('/leagues/league-5/seasons/season-1/schedule/unpublish')
      .send({})
      .expect(401);
  });

  it('rejects authenticated non-admin actor (403)', async () => {
    const agent = request.agent(app.getHttpServer());

    await agent
      .post('/auth/signup')
      .send({ email: 'user2@gridpilot.local', password: 'pw2', displayName: 'User 2' })
      .expect(201);

    await agent.post('/leagues/league-5/seasons/season-1/schedule/publish').send({}).expect(403);
    await agent.post('/leagues/league-5/seasons/season-1/schedule/unpublish').send({}).expect(403);
  });

  it('publish/unpublish toggles state and is reflected via schedule read (happy path)', async () => {
    const agent = request.agent(app.getHttpServer());

    await agent
      .post('/auth/login')
      .send({ email: 'admin@gridpilot.local', password: 'admin123' })
      .expect(201);

    const initialScheduleRes = await agent.get('/leagues/league-5/schedule?seasonId=season-1').expect(200);
    expect(initialScheduleRes.body).toMatchObject({
      seasonId: 'season-1',
      published: false,
      races: expect.any(Array),
    });

    await agent
      .post('/leagues/league-5/seasons/season-1/schedule/publish')
      .send({})
      .expect(200)
      .expect((res) => {
        expect(res.body).toEqual({ success: true, published: true });
      });

    const afterPublishRes = await agent.get('/leagues/league-5/schedule?seasonId=season-1').expect(200);
    expect(afterPublishRes.body).toMatchObject({
      seasonId: 'season-1',
      published: true,
      races: expect.any(Array),
    });

    await agent
      .post('/leagues/league-5/seasons/season-1/schedule/unpublish')
      .send({})
      .expect(200)
      .expect((res) => {
        expect(res.body).toEqual({ success: true, published: false });
      });

    const afterUnpublishRes = await agent.get('/leagues/league-5/schedule?seasonId=season-1').expect(200);
    expect(afterUnpublishRes.body).toMatchObject({
      seasonId: 'season-1',
      published: false,
      races: expect.any(Array),
    });
  });
});