import fs from "fs";
import path from "path";
import { DEFAULT_OPENAI_BASE_URL, SCHEMAS_DIR } from "../config";
import { ApiError } from "../utils/http";

export interface OpenAIRequestConfig {
  apiKey: string;
  model: string;
  baseUrl?: string;
  temperature?: number;
  maxOutputTokens?: number;
}

export interface OpenAIResponseEnvelope {
  raw: unknown;
  outputText: string;
  usage: {
    input_tokens: number;
    output_tokens: number;
    total_tokens: number;
  };
}

function extractUsage(raw: Record<string, unknown>): {
  input_tokens: number;
  output_tokens: number;
  total_tokens: number;
} {
  const usage = (raw.usage ?? {}) as Record<string, unknown>;
  const input = Number(usage.input_tokens ?? usage.prompt_tokens ?? 0);
  const output = Number(usage.output_tokens ?? usage.completion_tokens ?? 0);
  const total = Number(usage.total_tokens ?? input + output);
  return {
    input_tokens: Number.isFinite(input) ? input : 0,
    output_tokens: Number.isFinite(output) ? output : 0,
    total_tokens: Number.isFinite(total) ? total : 0
  };
}

function extractOutputText(raw: Record<string, unknown>): string {
  if (typeof raw.output_text === "string" && raw.output_text.trim().length > 0) {
    return raw.output_text;
  }

  const output = raw.output;
  if (Array.isArray(output)) {
    for (const item of output) {
      if (!item || typeof item !== "object") {
        continue;
      }
      const content = (item as Record<string, unknown>).content;
      if (!Array.isArray(content)) {
        continue;
      }
      for (const c of content) {
        if (!c || typeof c !== "object") {
          continue;
        }
        const block = c as Record<string, unknown>;
        if (typeof block.text === "string" && block.text.trim()) {
          return block.text;
        }
      }
    }
  }

  const response = raw.response;
  if (response && typeof response === "object") {
    const nested = response as Record<string, unknown>;
    if (typeof nested.output_text === "string" && nested.output_text.trim().length > 0) {
      return nested.output_text;
    }
  }

  throw new ApiError("OPENAI_OUTPUT_PARSE_FAILED", "Не удалось извлечь output_text из Responses API ответа.", 502, raw);
}

function loadSchemaForTransport(schemaVersion: "v1" | "v2" | "v2_0_1" | "v2_0_2"): Record<string, unknown> {
  const schemaFile =
    schemaVersion === "v1"
      ? "normalized_query_v1.json"
      : schemaVersion === "v2_0_1"
        ? "normalized_query_v2_0_1.json"
        : schemaVersion === "v2_0_2"
          ? "normalized_query_v2_0_2.json"
        : "normalized_query_v2.json";
  const schemaPath = path.resolve(SCHEMAS_DIR, schemaFile);
  return JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as Record<string, unknown>;
}

export class OpenAIResponsesClient {
  public async testConnection(config: OpenAIRequestConfig): Promise<{ ok: boolean; model: string }> {
    const payload = {
      model: config.model,
      input: [
        {
          role: "user",
          content: [{ type: "input_text", text: "ping" }]
        }
      ],
      max_output_tokens: 16
    };
    await this.post(config, payload);
    return { ok: true, model: config.model };
  }

  public async normalize(
    config: OpenAIRequestConfig,
    prompt: {
      systemPrompt: string;
      developerPrompt: string;
      domainPrompt: string;
      userQuestion: string;
      schemaVersion: "v1" | "v2" | "v2_0_1" | "v2_0_2";
      controlledRetryInstruction?: string;
    }
  ): Promise<OpenAIResponseEnvelope> {
    const schema = loadSchemaForTransport(prompt.schemaVersion);
    const schemaName =
      prompt.schemaVersion === "v1"
        ? "normalized_query_v1"
        : prompt.schemaVersion === "v2_0_1"
          ? "normalized_query_v2_0_1"
          : prompt.schemaVersion === "v2_0_2"
            ? "normalized_query_v2_0_2"
          : "normalized_query_v2";

    const developerPrompt = prompt.controlledRetryInstruction
      ? `${prompt.developerPrompt}\n\n${prompt.controlledRetryInstruction}`
      : prompt.developerPrompt;

    const payload = {
      model: config.model,
      temperature: config.temperature ?? 0,
      max_output_tokens: config.maxOutputTokens ?? 700,
      input: [
        {
          role: "system",
          content: [{ type: "input_text", text: prompt.systemPrompt }]
        },
        {
          role: "developer",
          content: [{ type: "input_text", text: developerPrompt }]
        },
        {
          role: "user",
          content: [
            {
              type: "input_text",
              text: `${prompt.domainPrompt}\n\nПользовательский вопрос:\n${prompt.userQuestion}`
            }
          ]
        }
      ],
      text: {
        format: {
          type: "json_schema",
          name: schemaName,
          strict: true,
          schema
        }
      }
    };

    const raw = await this.post(config, payload);
    const outputText = extractOutputText(raw);
    return {
      raw,
      outputText,
      usage: extractUsage(raw)
    };
  }

  private async post(config: OpenAIRequestConfig, payload: Record<string, unknown>): Promise<Record<string, unknown>> {
    if (!config.apiKey || config.apiKey.trim().length < 10) {
      throw new ApiError("OPENAI_API_KEY_MISSING", "API ключ OpenAI не задан или слишком короткий.", 400);
    }

    const url = `${(config.baseUrl ?? DEFAULT_OPENAI_BASE_URL).replace(/\/$/, "")}/responses`;
    const response = await fetch(url, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${config.apiKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(payload)
    });

    const text = await response.text();
    let data: Record<string, unknown>;
    try {
      data = JSON.parse(text) as Record<string, unknown>;
    } catch {
      throw new ApiError("OPENAI_NON_JSON_RESPONSE", "OpenAI вернул не-JSON ответ.", 502, { status: response.status, body: text.slice(0, 500) });
    }

    if (!response.ok) {
      const errorObj = (data.error ?? {}) as Record<string, unknown>;
      throw new ApiError(
        "OPENAI_REQUEST_FAILED",
        String(errorObj.message ?? `OpenAI request failed with status ${response.status}`),
        response.status,
        {
          status: response.status,
          type: errorObj.type ?? null,
          code: errorObj.code ?? null
        }
      );
    }

    return data;
  }
}
