import "reflect-metadata";
import { mkdirSync, writeFileSync } from "node:fs";
import * as path from "node:path";
import { NestFactory } from "@nestjs/core";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { AppModule } from "../app.module";

type JsonRecord = Record<string, unknown>;
type SwaggerPaths = Record<string, Record<string, JsonRecord>>;
type SwaggerSchemas = Record<string, JsonRecord>;

interface PostmanResponse {
  name: string;
  originalRequest: JsonRecord;
  status: string;
  code: number;
  _postman_previewlanguage: "json";
  header: Array<{ key: string; value: string }>;
  body: string;
}

interface PostmanItem {
  name: string;
  item?: PostmanItem[];
  request?: JsonRecord;
  response?: PostmanResponse[];
}

interface AiEndpointDefinition {
  name: string;
  method: "GET" | "POST";
  path: string;
  description: string;
  query?: Array<{ key: string; value: string; description: string }>;
  body?: JsonRecord;
  response: JsonRecord;
}

function refName(ref: string | undefined): string | null {
  if (!ref) {
    return null;
  }
  const parts = ref.split("/");
  return parts[parts.length - 1] ?? null;
}

function resolveSchema(
  schema: unknown,
  schemas: SwaggerSchemas,
): JsonRecord | null {
  if (!schema || typeof schema !== "object") {
    return null;
  }

  const schemaObject = schema as JsonRecord;
  const schemaRef =
    typeof schemaObject.$ref === "string" ? schemaObject.$ref : null;
  if (schemaRef) {
    const name = refName(schemaRef);
    return name ? (schemas[name] ?? null) : null;
  }

  return schemaObject;
}

function examplePrimitive(schema: JsonRecord): unknown {
  if (schema.example !== undefined) {
    return schema.example;
  }
  if (schema.default !== undefined) {
    return schema.default;
  }
  if (Array.isArray(schema.enum) && schema.enum.length > 0) {
    return schema.enum[0];
  }

  const type = typeof schema.type === "string" ? schema.type : "string";
  const format = typeof schema.format === "string" ? schema.format : "";

  if (type === "string") {
    if (format === "email") {
      return "user@example.com";
    }
    if (format === "date-time") {
      return "2026-04-14T00:00:00.000Z";
    }
    if (format === "date") {
      return "2026-04-14";
    }
    if (format === "uuid") {
      return "11111111-1111-1111-1111-111111111111";
    }
    return "string";
  }
  if (type === "integer" || type === "number") {
    return 1;
  }
  if (type === "boolean") {
    return true;
  }
  return "string";
}

function buildExampleFromSchema(
  schema: unknown,
  schemas: SwaggerSchemas,
  visited: Set<string> = new Set<string>(),
): unknown {
  const resolved = resolveSchema(schema, schemas);
  if (!resolved) {
    return null;
  }

  const schemaRef = typeof resolved.$ref === "string" ? resolved.$ref : null;
  if (schemaRef) {
    const name = refName(schemaRef);
    if (!name || visited.has(name)) {
      return null;
    }
    const nextVisited = new Set(visited);
    nextVisited.add(name);
    return buildExampleFromSchema(schemas[name], schemas, nextVisited);
  }

  if (Array.isArray(resolved.allOf) && resolved.allOf.length > 0) {
    return resolved.allOf.reduce<JsonRecord>((accumulator, part) => {
      const partial = buildExampleFromSchema(part, schemas, visited);
      if (partial && typeof partial === "object" && !Array.isArray(partial)) {
        return { ...accumulator, ...(partial as JsonRecord) };
      }
      return accumulator;
    }, {});
  }

  if (Array.isArray(resolved.oneOf) && resolved.oneOf.length > 0) {
    return buildExampleFromSchema(resolved.oneOf[0], schemas, visited);
  }

  if (Array.isArray(resolved.anyOf) && resolved.anyOf.length > 0) {
    return buildExampleFromSchema(resolved.anyOf[0], schemas, visited);
  }

  const type = typeof resolved.type === "string" ? resolved.type : "object";
  if (type === "array") {
    return [buildExampleFromSchema(resolved.items, schemas, visited)];
  }

  if (type === "object" || resolved.properties) {
    const properties = (resolved.properties ?? {}) as JsonRecord;
    const output: JsonRecord = {};
    for (const [key, value] of Object.entries(properties)) {
      output[key] = buildExampleFromSchema(value, schemas, visited);
    }
    if (Object.keys(output).length > 0) {
      return output;
    }
  }

  return examplePrimitive(resolved);
}

function swaggerSchemaFromContent(content: unknown): unknown {
  if (!content || typeof content !== "object") {
    return null;
  }
  const contentObject = content as JsonRecord;
  const jsonContent = contentObject["application/json"];
  if (jsonContent && typeof jsonContent === "object") {
    return (jsonContent as JsonRecord).schema ?? null;
  }
  const firstContent = Object.values(contentObject)[0];
  if (firstContent && typeof firstContent === "object") {
    return (firstContent as JsonRecord).schema ?? null;
  }
  return null;
}

function toPostmanPath(pathname: string): string {
  return pathname.replace(/\{([^}]+)\}/g, "{{$1}}");
}

function buildRequestBody(
  operation: JsonRecord,
  schemas: SwaggerSchemas,
): string | null {
  const requestBody = operation.requestBody as JsonRecord | undefined;
  const schema = swaggerSchemaFromContent(requestBody?.content);
  if (!schema) {
    return null;
  }

  const example = buildExampleFromSchema(schema, schemas);
  return JSON.stringify(example ?? {}, null, 2);
}

function buildResponses(
  operation: JsonRecord,
  method: string,
  rawPath: string,
  baseUrlVariable: string,
  schemas: SwaggerSchemas,
  body: string | null,
): PostmanResponse[] {
  const responses = (operation.responses ?? {}) as JsonRecord;
  const entries = Object.entries(responses);

  return entries.map(([statusCode, responseObject]) => {
    const responseRecord = responseObject as JsonRecord;
    const schema = swaggerSchemaFromContent(responseRecord.content);
    const example = buildExampleFromSchema(schema, schemas);
    const numericStatus = Number(statusCode);

    return {
      name: `${method.toUpperCase()} ${rawPath} - ${statusCode}`,
      originalRequest: {
        method: method.toUpperCase(),
        header: [{ key: "Content-Type", value: "application/json" }],
        body: body
          ? {
              mode: "raw",
              raw: body,
            }
          : undefined,
        url: {
          raw: `{{${baseUrlVariable}}}${toPostmanPath(rawPath)}`,
          host: [`{{${baseUrlVariable}}}`],
          path: rawPath.split("/").filter(Boolean),
        },
      },
      status:
        typeof responseRecord.description === "string"
          ? responseRecord.description
          : `HTTP ${statusCode}`,
      code: Number.isFinite(numericStatus) ? numericStatus : 200,
      _postman_previewlanguage: "json",
      header: [{ key: "Content-Type", value: "application/json" }],
      body: JSON.stringify(example ?? {}, null, 2),
    };
  });
}

function buildQueryParams(operation: JsonRecord): Array<JsonRecord> {
  const parameters = Array.isArray(operation.parameters)
    ? (operation.parameters as JsonRecord[])
    : [];

  return parameters
    .filter((parameter) => parameter.in === "query")
    .map((parameter) => ({
      key: String(parameter.name ?? ""),
      value:
        parameter.schema && typeof parameter.schema === "object"
          ? String((parameter.schema as JsonRecord).default ?? "")
          : "",
      description: String(parameter.description ?? ""),
      disabled: parameter.required === true ? false : true,
    }));
}

function buildHeaders(operation: JsonRecord): Array<JsonRecord> {
  const headers: Array<JsonRecord> = [
    {
      key: "Content-Type",
      value: "application/json",
    },
  ];

  const security = Array.isArray(operation.security)
    ? (operation.security as JsonRecord[])
    : [];
  if (security.length > 0) {
    headers.push({
      key: "Authorization",
      value: "Bearer {{accessToken}}",
    });
  }

  return headers;
}

function createRequestItem(
  name: string,
  method: string,
  rawPath: string,
  baseUrlVariable: string,
  operation: JsonRecord,
  schemas: SwaggerSchemas,
): PostmanItem {
  const body = buildRequestBody(operation, schemas);
  const query = buildQueryParams(operation);
  const headers = buildHeaders(operation);

  const request: JsonRecord = {
    method: method.toUpperCase(),
    header: headers,
    description:
      typeof operation.description === "string"
        ? operation.description
        : typeof operation.summary === "string"
          ? operation.summary
          : "",
    url: {
      raw: `{{${baseUrlVariable}}}${toPostmanPath(rawPath)}`,
      host: [`{{${baseUrlVariable}}}`],
      path: rawPath.split("/").filter(Boolean),
      query,
    },
  };

  if (body) {
    request.body = {
      mode: "raw",
      raw: body,
    };
  }

  return {
    name,
    request,
    response: buildResponses(
      operation,
      method,
      rawPath,
      baseUrlVariable,
      schemas,
      body,
    ),
  };
}

function buildNestFolders(document: JsonRecord): PostmanItem[] {
  const paths = (document.paths ?? {}) as SwaggerPaths;
  const schemas = ((document.components ?? {}) as JsonRecord).schemas as
    | SwaggerSchemas
    | undefined;
  const safeSchemas = schemas ?? {};

  const folders = new Map<string, PostmanItem[]>();

  for (const [rawPath, pathItem] of Object.entries(paths)) {
    for (const [method, operationObject] of Object.entries(pathItem)) {
      if (!["get", "post", "put", "patch", "delete"].includes(method)) {
        continue;
      }

      const operation = operationObject;
      const tags = Array.isArray(operation.tags) ? operation.tags : [];
      const folderName =
        typeof tags[0] === "string" && tags[0].trim().length > 0
          ? tags[0]
          : "Misc";
      const requestName =
        typeof operation.summary === "string" &&
        operation.summary.trim().length > 0
          ? operation.summary
          : `${method.toUpperCase()} ${rawPath}`;

      const item = createRequestItem(
        requestName,
        method,
        rawPath,
        "beBaseUrl",
        operation,
        safeSchemas,
      );

      const existing = folders.get(folderName) ?? [];
      existing.push(item);
      folders.set(folderName, existing);
    }
  }

  return [...folders.entries()]
    .sort((a, b) => a[0].localeCompare(b[0]))
    .map(([folderName, items]) => ({
      name: folderName,
      item: items.sort((a, b) => a.name.localeCompare(b.name)),
    }));
}

function createAiRequest(
  endpoint: AiEndpointDefinition,
  folderName: string,
): PostmanItem {
  const url: JsonRecord = {
    raw: `{{aiBaseUrl}}${endpoint.path}`,
    host: ["{{aiBaseUrl}}"],
    path: endpoint.path.split("/").filter(Boolean),
  };

  if (endpoint.query && endpoint.query.length > 0) {
    url.query = endpoint.query.map((queryItem) => ({
      key: queryItem.key,
      value: queryItem.value,
      description: queryItem.description,
    }));
  }

  const request: JsonRecord = {
    method: endpoint.method,
    header: [{ key: "Content-Type", value: "application/json" }],
    description: endpoint.description,
    url,
  };

  if (endpoint.body) {
    request.body = {
      mode: "raw",
      raw: JSON.stringify(endpoint.body, null, 2),
    };
  }

  return {
    name: endpoint.name,
    request,
    response: [
      {
        name: `${endpoint.method} ${endpoint.path}`,
        originalRequest: request,
        status: "OK",
        code: 200,
        _postman_previewlanguage: "json",
        header: [{ key: "Content-Type", value: "application/json" }],
        body: JSON.stringify(endpoint.response, null, 2),
      },
    ],
  };
}

function buildAiFolder(): PostmanItem {
  const v20Endpoints: AiEndpointDefinition[] = [
    {
      name: "Root",
      method: "GET",
      path: "/",
      description: "AI engine root status endpoint",
      response: {
        status: "Suggest-Bet AI Engine v20+",
        engine: "V20 Plus Single Match Orchestrator",
      },
    },
    {
      name: "Health",
      method: "GET",
      path: "/health",
      description: "AI engine health endpoint",
      response: { status: "healthy", engine: "v20plus", ready: true },
    },
    {
      name: "Analyze Match",
      method: "POST",
      path: "/v20plus/analyze/{{match_id}}",
      description: "Full V20+ single match analysis",
      response: {
        model_version: "v30.0",
        match_info: { match_id: "{{match_id}}" },
        main_pick: { market: "OU25", pick: "2.5 Üst" },
        market_board: {},
      },
    },
    {
      name: "Analyze HTMS",
      method: "GET",
      path: "/v20plus/analyze-htms/{{match_id}}",
      description: "Half-time result analysis endpoint",
      response: { match_id: "{{match_id}}", market: "HT" },
    },
    {
      name: "Analyze HTFT",
      method: "GET",
      path: "/v20plus/analyze-htft/{{match_id}}",
      description: "Half-time/full-time analysis endpoint",
      query: [
        {
          key: "timeout_sec",
          value: "30",
          description: "Timeout between 3 and 120 seconds",
        },
      ],
      response: {
        engine: "v20plus.1",
        match_info: { match_id: "{{match_id}}" },
        ht_ft_probs: { "1/1": 0.25, "X/X": 0.18 },
      },
    },
    {
      name: "Generate Coupon",
      method: "POST",
      path: "/v20plus/coupon",
      description: "Generate V20+ coupon from selected matches",
      body: {
        match_ids: ["match-1", "match-2"],
        strategy: "BALANCED",
        max_matches: 4,
        min_confidence: 55,
      },
      response: {
        success: true,
        data: {
          strategy: "BALANCED",
          bets: [],
        },
      },
    },
    {
      name: "Daily Banker",
      method: "GET",
      path: "/v20plus/daily-banker",
      description: "Get daily banker picks",
      query: [
        {
          key: "count",
          value: "3",
          description: "Number of banker picks",
        },
      ],
      response: { count: 3, bankers: [] },
    },
    {
      name: "Reversal Watchlist",
      method: "GET",
      path: "/v20plus/reversal-watchlist",
      description: "Reversal watchlist candidates",
      query: [
        { key: "count", value: "20", description: "Result size" },
        { key: "horizon_hours", value: "72", description: "Future horizon" },
        { key: "min_score", value: "45", description: "Minimum score" },
        {
          key: "top_leagues_only",
          value: "false",
          description: "Filter to top leagues",
        },
      ],
      response: { count: 0, items: [] },
    },
  ];

  const v2Endpoints: AiEndpointDefinition[] = [
    {
      name: "V2 Health",
      method: "GET",
      path: "/v2/health",
      description: "V2 betting engine health",
      response: {
        status: "healthy",
        engine: "v2.betting_engine",
        models_loaded: true,
      },
    },
    {
      name: "V2 Analyze Match",
      method: "POST",
      path: "/v2/analyze/{{match_id}}",
      description: "V2 leakage-free match analysis",
      response: {
        model_version: "v2.betting_engine",
        match_info: { match_id: "{{match_id}}" },
        main_pick: { market: "MS", pick: "1" },
        market_board: {
          MS: { pick: "1", confidence: 58.4 },
        },
      },
    },
  ];

  return {
    name: "AI Engine",
    item: [
      {
        name: "V20+",
        item: v20Endpoints.map((endpoint) => createAiRequest(endpoint, "V20+")),
      },
      {
        name: "V2",
        item: v2Endpoints.map((endpoint) => createAiRequest(endpoint, "V2")),
      },
    ],
  };
}

async function run(): Promise<void> {
  const projectRoot = process.cwd();
  const outputDir = path.join(projectRoot, "mds");
  const outputFile = path.join(
    outputDir,
    "suggest-bet-platform.postman_collection.json",
  );

  process.env.REDIS_ENABLED = "true";

  const app = await NestFactory.create(AppModule, { logger: false });
  app.setGlobalPrefix("api");

  const swaggerConfig = new DocumentBuilder()
    .setTitle("Suggest Bet Backend API")
    .setDescription("Postman collection export source")
    .setVersion("1.0")
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(
    app,
    swaggerConfig,
  ) as unknown as JsonRecord;

  const collection: JsonRecord = {
    info: {
      name: "Suggest-Bet Platform API",
      description:
        "Auto-generated Postman collection for Nest backend and AI engine endpoints.",
      schema:
        "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    },
    variable: [
      { key: "beBaseUrl", value: "http://localhost:3005" },
      { key: "aiBaseUrl", value: "http://localhost:8000" },
      { key: "accessToken", value: "" },
      { key: "match_id", value: "sample-match-id" },
    ],
    auth: {
      type: "bearer",
      bearer: [{ key: "token", value: "{{accessToken}}", type: "string" }],
    },
    item: [
      {
        name: "Nest API",
        item: buildNestFolders(document),
      },
      buildAiFolder(),
    ],
  };

  mkdirSync(outputDir, { recursive: true });
  writeFileSync(outputFile, JSON.stringify(collection, null, 2), "utf8");

  await app.close();

  console.log(`✅ Postman collection exported: ${outputFile}`);
}

void run();
