import { randomUUID } from 'node:crypto';
import { INestApplication, ValidationPipe, VersioningType } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { AppModule } from '../../src/app.module';
import { AppConfigService } from '../../src/modules/config/config.service';
import { DevicesController } from '../../src/modules/devices/devices.controller';
import { HealthController } from '../../src/modules/health/health.controller';
import { SyncController } from '../../src/modules/sync/sync.controller';
import { LocalFilesystemStorageService } from '../../src/modules/storage/storage.service';
import { PrismaService } from '../../src/infrastructure/database/prisma.service';

function createPrismaMock() {
  const devices = new Map<string, any>();

  return {
    $queryRawUnsafe: jest.fn().mockResolvedValue([{ '?column?': 1 }]),
    device: {
      create: jest.fn().mockImplementation(async ({ data }) => {
        const id = randomUUID();
        const record = { id, ...data };
        devices.set(id, record);
        return record;
      }),
      findUnique: jest.fn().mockImplementation(async ({ where }) => {
        return devices.get(where.id) ?? null;
      }),
      update: jest.fn().mockImplementation(async ({ where, data }) => {
        const current = devices.get(where.id);
        const updated = { ...current, ...data };
        devices.set(where.id, updated);
        return updated;
      }),
    },
    audioAsset: {
      findUnique: jest.fn().mockResolvedValue(null),
    },
    uploadSession: {
      create: jest.fn().mockImplementation(async ({ data }) => {
        return {
          id: randomUUID(),
          ...data,
        };
      }),
      findUnique: jest.fn().mockResolvedValue(null),
    },
    libraryEvent: {
      findFirst: jest.fn().mockResolvedValue(null),
    },
  };
}

describe('Velody API wiring (e2e)', () => {
  let app: INestApplication;
  let healthController: HealthController;
  let devicesController: DevicesController;
  let syncController: SyncController;

  beforeEach(async () => {
    const prismaMock = createPrismaMock();
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(AppConfigService)
      .useValue({
        appVersion: '0.1.0',
        maxUploadSizeBytes: 1024 * 1024 * 1024,
        storageRoot: '/tmp/velody-storage',
      })
      .overrideProvider(LocalFilesystemStorageService)
      .useValue({
        root: '/tmp/velody-storage',
        checkReadiness: jest.fn().mockResolvedValue({
          root: '/tmp/velody-storage',
          writable: true,
        }),
      })
      .overrideProvider(PrismaService)
      .useValue(prismaMock)
      .compile();

    app = moduleRef.createNestApplication();
    app.setGlobalPrefix('api');
    app.enableVersioning({ type: VersioningType.URI });
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );
    await app.init();

    healthController = moduleRef.get(HealthController);
    devicesController = moduleRef.get(DevicesController);
    syncController = moduleRef.get(SyncController);
  });

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

  it('returns health information', async () => {
    const response = await healthController.getHealth();

    expect(response.database.status).toBe('up');
    expect(response.storage.status).toBe('up');
    expect(response.version).toBe('0.1.0');
  });

  it('registers a device', async () => {
    const response = await devicesController.register({
      platform: 'MACOS',
      deviceName: 'Diya MacBook Pro',
      appVersion: '0.1.0',
    });

    expect(response.deviceId).toBeDefined();
    expect(response.bootstrapToken).toBeDefined();
  });

  it('accepts device heartbeat', async () => {
    const registerResponse = await devicesController.register({
      platform: 'IPHONE',
      deviceName: 'Diya iPhone',
      appVersion: '0.1.0',
    });

    const response = await devicesController.heartbeat({
      deviceId: registerResponse.deviceId,
      appVersion: '0.1.1',
    });

    expect(response.ok).toBe(true);
  });

  it('returns empty sync bootstrap and changes payloads', async () => {
    const bootstrapResponse = await syncController.bootstrap();
    const changesResponse = await syncController.changes({ after: '0' });

    expect(bootstrapResponse.tracks).toEqual([]);
    expect(changesResponse.events).toEqual([]);
    expect(changesResponse.nextCursor).toBe('0');
  });
});
