import 'reflect-metadata';
import {
  type CanActivate,
  type ExecutionContext,
  type INestApplication,
  ValidationPipe,
} 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 { ProjectBootstrapService } from './project-bootstrap.service.js';
import { WorkspaceController } from './workspace.controller.js';

const bootstrapMock = vi.fn(() =>
  Promise.resolve({
    projectId: 'project-1',
    workspacePath: '/opt/mosaic/.workspaces/users/user-1/project-1',
  }),
);

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

describe('POST /api/workspaces repoUrl validation', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [WorkspaceController],
      providers: [
        {
          provide: ProjectBootstrapService,
          useValue: { bootstrap: bootstrapMock },
        },
      ],
    })
      .overrideGuard(AuthGuard)
      .useValue(authGuard)
      .compile();

    app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );
    await app.init();
    await app.getHttpAdapter().getInstance().ready();
  });

  beforeEach(() => {
    bootstrapMock.mockClear();
  });

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

  it.each([
    ['a leading-dash value', '--upload-pack=sh -c id'],
    ['an ext remote helper', 'ext::sh -c id'],
    ['a file URL', 'file:///tmp/repository'],
    ['an unparseable value', 'not a url'],
    ['an SSH shorthand', 'git@example.com:acme/repository.git'],
    ['a scheme without //', 'https:example.com/acme/repository.git'],
    ['a hostless git URL', 'git:///tmp/repository'],
  ])('returns 400 for %s', async (_description, repoUrl) => {
    const response = await request(app.getHttpServer())
      .post('/api/workspaces')
      .send({ name: 'Example', repoUrl })
      .set('Content-Type', 'application/json');

    expect(response.status).toBe(400);
    expect(bootstrapMock).not.toHaveBeenCalled();
  });

  it.each([
    ['a plain HTTPS repository URL', 'https://example.com/acme/repository.git'],
    ['a git protocol repository URL', 'git://example.com/acme/repository.git'],
  ])('accepts %s', async (_description, repoUrl) => {
    const response = await request(app.getHttpServer())
      .post('/api/workspaces')
      .send({ name: 'Example', repoUrl })
      .set('Content-Type', 'application/json');

    expect(response.status).toBe(201);
    expect(bootstrapMock).toHaveBeenCalledWith({
      name: 'Example',
      description: undefined,
      userId: 'user-1',
      teamId: undefined,
      repoUrl,
    });
  });
});
