import { NestFactory } from '@nestjs/core';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
import { AppModule } from '../src/app.module';
import { InboundBuilderService } from '../src/inbounds/inbound-builder.service';
import { XuiInboundRaw } from '../src/inbounds/xui-inbound.types';
import { Node } from '../src/nodes/entities/node.entity';
import { XuiService } from '../src/xui/xui.service';

const SNI = process.env.SMOKE_SNI || 'www.cloudflare.com';

type SmokeResult = {
  type: string;
  port: number;
  success: boolean;
  xuiId?: number | null;
  deleted?: boolean;
  error?: string;
};

const randomPort = () => Math.floor(Math.random() * (60000 - 20000 + 1)) + 20000;

async function main() {
  console.log('Starting inbound smoke test...');
  const app = await NestFactory.createApplicationContext(AppModule, {
    logger: ['error', 'warn', 'log'],
  });
  console.log('Application context is ready.');

  const nodeRepo = app.get<Repository<Node>>(getRepositoryToken(Node));
  const xuiService = app.get(XuiService);
  const builder = app.get(InboundBuilderService);

  const node = await nodeRepo
    .createQueryBuilder('node')
    .addSelect('node.password')
    .addSelect('node.token')
    .where('node.isMain = :isMain', { isMain: true })
    .getOne();

  if (!node) {
    throw new Error('Main node not found');
  }
  console.log(`Main node loaded: ${node.name}`);

  const keys = await xuiService.getNewX25519Cert(node);
  if (!keys) {
    throw new Error('Could not get Reality keys from the main node');
  }
  console.log('Reality keys received.');

  const buildCases: Array<{
    type: string;
    build: (port: number, uuid: string) => XuiInboundRaw;
  }> = [
    {
      type: 'vless-tcp-reality',
      build: (port, uuid) => builder.buildVlessRealityTcp({ port, uuid, sni: SNI, ...keys }),
    },
    {
      type: 'vless-xhttp-reality',
      build: (port, uuid) => builder.buildVlessRealityXhttp({ port, uuid, sni: SNI, ...keys }),
    },
    {
      type: 'vless-grpc-reality',
      build: (port, uuid) => builder.buildVlessRealityGrpc({ port, uuid, sni: SNI, ...keys }),
    },
    {
      type: 'vless-ws',
      build: (port, uuid) => builder.buildVlessWs({ port, uuid, sni: SNI }),
    },
    {
      type: 'vmess-tcp',
      build: (port, uuid) => builder.buildVmessTcp({ port, uuid }),
    },
    {
      type: 'shadowsocks-tcp',
      build: (port, uuid) => builder.buildShadowsocksTcp({ port, uuid }),
    },
    {
      type: 'trojan-tcp-reality',
      build: (port, uuid) => builder.buildTrojanRealityTcp({ port, uuid, sni: SNI, ...keys }),
    },
    {
      type: 'hysteria2-udp',
      build: (port, uuid) => builder.buildHysteria2Inbound({ port, uuid, sni: SNI }),
    },
  ];

  const results: SmokeResult[] = [];

  for (const testCase of buildCases) {
    const port = randomPort();
    const uuid = uuidv4();
    let xuiId: number | null = null;

    try {
      console.log(`Testing ${testCase.type} on port ${port}...`);
      const config = testCase.build(port, uuid);
      config.remark = `smoke-${testCase.type}-${Date.now()}`;
      xuiId = await xuiService.addInbound(config, node);

      if (xuiId) {
        console.log(`${testCase.type}: created with xuiId=${xuiId}; deleting...`);
        await xuiService.deleteInbound(xuiId, node);
      }

      results.push({
        type: testCase.type,
        port,
        success: Boolean(xuiId),
        xuiId,
        deleted: Boolean(xuiId),
      });
      console.log(`${testCase.type}: ${xuiId ? 'ok' : 'failed'}`);
    } catch (error) {
      if (xuiId) {
        await xuiService.deleteInbound(xuiId, node);
      }
      results.push({
        type: testCase.type,
        port,
        success: false,
        xuiId,
        deleted: Boolean(xuiId),
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  }

  console.table(results);
  await app.close();

  const failed = results.filter((result) => !result.success);
  process.exitCode = failed.length > 0 ? 1 : 0;
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : error);
  process.exitCode = 1;
});
