import { spawn } from "child_process";
import crypto from "crypto";

const DEFAULT_TIMEOUT_MS = 45_000;
const DEFAULT_MAX_TURNS = "1";
const QODER_DEFAULT_MODEL = "qoder-rome-30ba3b";

export const QODER_STATIC_MODELS = [
  { id: "qoder-rome-30ba3b", name: "Qoder ROME" },
  { id: "qwen3-coder-plus", name: "Qwen3 Coder Plus" },
  { id: "qwen3-max", name: "Qwen3 Max" },
  { id: "qwen3-vl-plus", name: "Qwen3 Vision Plus" },
  { id: "kimi-k2-0905", name: "Kimi K2 0905" },
  { id: "qwen3-max-preview", name: "Qwen3 Max Preview" },
  { id: "kimi-k2", name: "Kimi K2" },
  { id: "deepseek-v3.2", name: "DeepSeek V3.2" },
  { id: "deepseek-r1", name: "DeepSeek R1" },
  { id: "deepseek-v3", name: "DeepSeek V3" },
  { id: "qwen3-32b", name: "Qwen3 32B" },
  { id: "qwen3-235b-a22b-thinking-2507", name: "Qwen3 235B A22B Thinking 2507" },
  { id: "qwen3-235b-a22b-instruct", name: "Qwen3 235B A22B Instruct" },
  { id: "qwen3-235b", name: "Qwen3 235B" },
];

type JsonRecord = Record<string, unknown>;

type QoderCliRunOptions = {
  token: string;
  prompt: string;
  stream: boolean;
  model?: string | null;
  workspace?: string | null;
  command?: string | null;
  signal?: AbortSignal | null;
  timeoutMs?: number;
};

type QoderCliRunResult = {
  ok: boolean;
  code: number | null;
  stdout: string;
  stderr: string;
  timedOut: boolean;
  error: string | null;
};

type QoderCliFailure = {
  status: number;
  message: string;
  code: string;
};

function asRecord(value: unknown): JsonRecord {
  return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}

function getString(value: unknown): string {
  return typeof value === "string" ? value : "";
}

export function getQoderCliCommand(): string {
  const explicit = String(process.env.CLI_QODER_BIN || "").trim();
  return explicit || "qodercli";
}

export function getQoderCliWorkspace(): string {
  const explicit = String(
    process.env.QODER_CLI_WORKSPACE || process.env.OMNIROUTE_QODER_WORKSPACE || ""
  ).trim();
  if (explicit) return explicit;
  const home = String(process.env.HOME || "").trim();
  return home || process.cwd();
}

export function normalizeQoderPatProviderData(providerSpecificData: JsonRecord = {}): JsonRecord {
  return {
    ...providerSpecificData,
    authMode: "pat",
    transport: "qodercli",
  };
}

export function isQoderCliTransport(providerSpecificData: unknown = {}): boolean {
  const data = asRecord(providerSpecificData);
  const transport = getString(data.transport).trim().toLowerCase();
  const authMode = getString(data.authMode).trim().toLowerCase();
  if (transport === "http-legacy") return false;
  return transport === "qodercli" || authMode === "pat";
}

export function getStaticQoderModels() {
  return QODER_STATIC_MODELS.map((model) => ({ ...model }));
}

export function mapQoderModelToLevel(model: string | null | undefined): string | null {
  const normalized = String(model || "")
    .trim()
    .toLowerCase();
  if (!normalized) return null;
  if (normalized.includes("deepseek-r1")) return "ultimate";
  if (normalized.includes("qwen3-max")) return "performance";
  if (normalized.includes("kimi-k2")) return "kmodel";
  if (normalized.includes("qwen3-coder")) return "qmodel";
  if (normalized.includes("qoder-rome")) return "qmodel";
  return "auto";
}

function flattenMessageContent(content: unknown): string {
  if (typeof content === "string") return content;
  if (!Array.isArray(content)) return "";

  return content
    .map((item) => {
      if (typeof item === "string") return item;
      if (!item || typeof item !== "object") return "";

      const record = item as JsonRecord;
      const itemType = getString(record.type);
      if (itemType === "text" || itemType === "input_text") {
        return getString(record.text);
      }
      if (itemType === "image_url" || itemType === "input_image") {
        return "[Image omitted]";
      }
      return "";
    })
    .filter(Boolean)
    .join("\n");
}

function formatMessage(message: unknown): string {
  if (!message || typeof message !== "object") return "";
  const record = message as JsonRecord;
  const role = getString(record.role).trim().toUpperCase() || "UNKNOWN";
  const base = flattenMessageContent(record.content);

  if (role === "TOOL") {
    const toolName = getString(record.name).trim();
    return `TOOL${toolName ? ` (${toolName})` : ""}:\n${base}`.trim();
  }

  const toolCalls = Array.isArray(record.tool_calls) ? record.tool_calls : [];
  if (toolCalls.length > 0) {
    const toolLines = toolCalls
      .map((toolCall) => {
        const toolRecord = asRecord(toolCall);
        const functionRecord = asRecord(toolRecord.function);
        const toolName =
          getString(functionRecord.name).trim() || getString(toolRecord.name).trim() || "tool";
        const toolArgs =
          getString(functionRecord.arguments).trim() || getString(toolRecord.arguments).trim();
        return `TOOL_CALL ${toolName}: ${toolArgs}`.trim();
      })
      .filter(Boolean)
      .join("\n");

    return `${role}:\n${base}\n${toolLines}`.trim();
  }

  return `${role}:\n${base}`.trim();
}

export function buildQoderPrompt(body: unknown): string {
  const requestBody = asRecord(body);
  const lines = [
    "You are answering an OmniRoute OpenAI-compatible request through the Qoder CLI transport.",
    "Respond as a plain language model only.",
    "Do not use your own tools, do not inspect files, and do not run commands.",
    "Do not mention the adapter unless the user explicitly asks.",
  ];

  const tools = Array.isArray(requestBody.tools) ? requestBody.tools : [];
  if (tools.length > 0) {
    const toolNames = tools
      .map((tool) => {
        const toolRecord = asRecord(tool);
        const functionRecord =
          toolRecord.type === "function" ? asRecord(toolRecord.function) : toolRecord;
        return getString(functionRecord.name).trim();
      })
      .filter(Boolean)
      .join(", ");

    if (toolNames) {
      lines.push(`Caller-side tools are available externally: ${toolNames}.`);
      lines.push("Do not call those tools yourself. Answer in assistant text only.");
    }
  }

  const responseFormat = asRecord(requestBody.response_format);
  if (responseFormat.type === "json_object") {
    lines.push("Return only valid JSON.");
  } else if (
    responseFormat.type === "json_schema" &&
    responseFormat.json_schema &&
    typeof responseFormat.json_schema === "object"
  ) {
    const jsonSchema = asRecord(responseFormat.json_schema);
    if (jsonSchema.schema && typeof jsonSchema.schema === "object") {
      lines.push(
        `Return only valid JSON matching this schema:\n${JSON.stringify(jsonSchema.schema, null, 2)}`
      );
    }
  }

  const messages = Array.isArray(requestBody.messages)
    ? requestBody.messages
    : Array.isArray(requestBody.input)
      ? requestBody.input
      : [];

  if (messages.length > 0) {
    lines.push("Conversation transcript:");
    for (const message of messages) {
      const formatted = formatMessage(message);
      if (formatted) lines.push(formatted);
    }
  }

  lines.push("Reply now with the assistant response only.");
  return lines.filter(Boolean).join("\n\n");
}

export function extractTextFromQoderEnvelope(parsed: unknown): string {
  const record = asRecord(parsed);
  const messageRecord = asRecord(record.message);
  const content = messageRecord.content ?? record.content ?? record.delta ?? record.text ?? null;

  if (typeof content === "string") return content;
  if (!Array.isArray(content)) return "";

  return content
    .map((item) => {
      const itemRecord = asRecord(item);
      const itemType = getString(itemRecord.type).trim();
      if (itemType === "text" || !itemType) {
        return getString(itemRecord.text);
      }
      return "";
    })
    .filter(Boolean)
    .join("");
}

export function buildQoderCompletionPayload({
  model,
  text,
}: {
  model?: string | null;
  text: string;
}) {
  const created = Math.floor(Date.now() / 1000);
  return {
    id: `chatcmpl-${crypto.randomUUID()}`,
    object: "chat.completion",
    created,
    model: model || QODER_DEFAULT_MODEL,
    choices: [
      {
        index: 0,
        message: {
          role: "assistant",
          content: text,
        },
        finish_reason: "stop",
      },
    ],
    usage: {
      prompt_tokens: 0,
      completion_tokens: 0,
      total_tokens: 0,
    },
  };
}

export function buildQoderChunk({
  id,
  model,
  created,
  delta,
  finishReason = null,
}: {
  id: string;
  model: string;
  created: number;
  delta: Record<string, unknown>;
  finishReason?: string | null;
}) {
  return {
    id,
    object: "chat.completion.chunk",
    created,
    model,
    choices: [
      {
        index: 0,
        delta,
        finish_reason: finishReason,
      },
    ],
  };
}

export function parseQoderCliFailure(stderrText: string, stdoutText = ""): QoderCliFailure {
  const stderr = String(stderrText || "").trim();
  const stdout = String(stdoutText || "").trim();
  const combined = `${stderr}\n${stdout}`.trim() || "Qoder API request failed";
  const normalized = combined.toLowerCase();

  if (
    normalized.includes("invalid api key") ||
    normalized.includes("invalid token") ||
    normalized.includes("personal access token") ||
    (normalized.includes("unauthorized") && normalized.includes("qoder"))
  ) {
    return { status: 401, message: combined, code: "upstream_auth_error" };
  }

  if (normalized.includes("timed out") || normalized.includes("timeout")) {
    return { status: 504, message: combined, code: "timeout" };
  }

  return { status: 502, message: combined, code: "upstream_error" };
}

export function createQoderErrorResponse(failure: QoderCliFailure): Response {
  return new Response(
    JSON.stringify({
      error: {
        message: failure.message,
        type: failure.status === 401 ? "authentication_error" : "provider_error",
        code: failure.code,
      },
    }),
    {
      status: failure.status,
      headers: {
        "Content-Type": "application/json",
      },
    }
  );
}

const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8iMH5c02LilrsERw9t6Pv5Nc
4k6Pz1EaDicBMpdpxKduSZu5OANqUq8er4GM95omAGIOPOh+Nx0spthYA2BqGz+l
6HRkPJ7S236FZz73In/KVuLnwI8JJ2CbuJap8kvheCCZpmAWpb/cPx/3Vr/J6I17
XcW+ML9FoCI6AOvOzwIDAQAB
-----END PUBLIC KEY-----`;

export function buildCosyHeadersForValidation(bodyStr: string, token: string) {
  const aesKeyBytes = crypto.randomBytes(16);
  const aesKeyStr = aesKeyBytes.toString("hex").slice(0, 16);
  const aesKeyBuf = Buffer.from(aesKeyStr, "utf8");

  const uid = "omniroute.user@qoder.sh";
  const userInfo = {
    uid: uid,
    security_oauth_token: token,
    name: "omniroute",
    aid: "",
    email: uid,
  };

  const cipher = crypto.createCipheriv("aes-128-cbc", aesKeyBuf, aesKeyBuf);
  let ciphertext = cipher.update(JSON.stringify(userInfo), "utf8", "base64");
  ciphertext += cipher.final("base64");

  const encryptedKeyBuf = crypto.publicEncrypt(
    { key: PUBLIC_KEY, padding: crypto.constants.RSA_PKCS1_PADDING },
    aesKeyBuf
  );
  const cosyKeyB64 = encryptedKeyBuf.toString("base64");
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const payloadStr = JSON.stringify({
    version: "v1",
    requestId: crypto.randomUUID(),
    info: ciphertext,
    cosyVersion: "0.12.3",
    ideVersion: "",
  });
  const payloadB64 = Buffer.from(payloadStr).toString("base64");
  const sigPath = "/api/v2/service/pro/sse/agent_chat_generation";
  const sigInput = `${payloadB64}\n${cosyKeyB64}\n${timestamp}\n${bodyStr}\n${sigPath}`;
  const sig = crypto.createHash("md5").update(sigInput).digest("hex");

  return {
    Authorization: `Bearer COSY.${payloadB64}.${sig}`,
    "Cosy-Key": cosyKeyB64,
    "Cosy-User": uid,
    "Cosy-Date": timestamp,
    "Content-Type": "application/json",
  };
}

export async function validateQoderCliPat({
  apiKey,
  providerSpecificData = {},
}: {
  apiKey: string;
  providerSpecificData?: JsonRecord;
}) {
  // Resolve token: dashboard input → env var fallback
  const resolvedToken =
    apiKey?.trim() || String(process.env.QODER_PERSONAL_ACCESS_TOKEN || "").trim();

  if (!resolvedToken) {
    return {
      valid: false,
      error:
        "No Qoder token provided. Get your Personal Access Token from https://qoder.com/account/integrations or set QODER_PERSONAL_ACCESS_TOKEN env var.",
      unsupported: false,
    };
  }

  // PAT format guidance: Qoder PATs should be non-empty strings.
  // Warn if the token looks like it might be an encrypted blob (from ~/.qoder/.auth/user)
  // rather than a proper PAT from the website.
  if (resolvedToken.length > 500) {
    return {
      valid: false,
      error:
        "Token appears to be an encrypted auth blob (from ~/.qoder/.auth/user). " +
        "Please use a Personal Access Token from https://qoder.com/account/integrations instead.",
      unsupported: false,
    };
  }

  const modelId =
    getString(providerSpecificData.validationModelId).trim() ||
    getString(providerSpecificData.modelId).trim() ||
    QODER_DEFAULT_MODEL;

  const bodyStr = JSON.stringify({
    model: modelId || "coder-model",
    messages: [{ role: "user", content: "hi" }],
    stream: false,
  });

  // Step 1: Connectivity check — verify Qoder API is reachable
  try {
    const pingRes = await fetch("https://api1.qoder.sh/algo/api/v1/ping", {
      method: "GET",
      // @ts-ignore
      signal: AbortSignal.timeout(10000),
    });
    if (!pingRes.ok) {
      return {
        valid: false,
        error: `Qoder API unreachable (ping returned ${pingRes.status}). Check your network/proxy configuration.`,
        unsupported: false,
      };
    }
  } catch (pingErr: any) {
    return {
      valid: false,
      error:
        `Cannot reach Qoder API (${pingErr.message}). ` +
        "If behind a proxy, configure HTTPS_PROXY. For Docker, ensure the container has internet access.",
      unsupported: false,
    };
  }

  // Step 2: Auth validation — send a minimal request with the PAT
  const headers = buildCosyHeadersForValidation(bodyStr, resolvedToken);
  const endpoint =
    "https://api1.qoder.sh/algo/api/v2/service/pro/sse/agent_chat_generation?AgentId=agent_common";

  try {
    const res = await fetch(endpoint, {
      method: "POST",
      headers,
      body: bodyStr,
      // @ts-ignore
      signal: AbortSignal.timeout(30000),
    });

    if (res.ok || res.status === 200) {
      return { valid: true, error: null, unsupported: false };
    }

    // Parse error body for better diagnostics
    let errorDetail = "";
    try {
      const errBody = await res.text();
      errorDetail = errBody.slice(0, 300);
    } catch {}

    if (res.status === 401 || res.status === 403) {
      return {
        valid: false,
        error:
          `Authentication failed (HTTP ${res.status}). ` +
          "Make sure you're using a valid Personal Access Token from https://qoder.com/account/integrations. " +
          "Note: tokens from ~/.qoder/.auth/user are encrypted and cannot be used directly." +
          (errorDetail ? ` Server: ${errorDetail}` : ""),
        unsupported: false,
      };
    }

    // 4xx other than auth — token was accepted but request had issues (model, format, etc.)
    if (res.status >= 400 && res.status < 500) {
      return { valid: true, error: null, unsupported: false };
    }

    // Treat 5xx as a valid bypass to prevent false negatives from legacy Qoder APIs (#1391).
    // A Cosy `{"success":false}` 500 is ambiguous: it can be a genuine auth rejection OR a
    // transient/generic upstream "Internal Server Error". Only mark the PAT invalid when the
    // body carries an EXPLICIT auth signal — a generic 500 is a server fault, not an auth
    // verdict, so a working PAT must not be reported as expired (#3247, narrowing #2860).
    if (res.status >= 500) {
      const isCosyResponse = /"success"\s*:\s*false/.test(errorDetail);
      const hasAuthSignal =
        /(unauthorized|forbidden|expired|revoked|not\s*authorized|permission\s*denied|access\s*denied|invalid\s*(?:token|credential|api[\s_-]*key)|token\s*(?:invalid|expired|revoked))/i.test(
          errorDetail
        );

      if (isCosyResponse && hasAuthSignal) {
        return {
          valid: false,
          error:
            `Authentication failed (HTTP ${res.status}). The Qoder Cosy server rejected the token ` +
            "as invalid, expired, or not authorized. " +
            "Please check your token at https://qoder.com/account/integrations." +
            (errorDetail ? ` Server response: ${errorDetail}` : ""),
          unsupported: false,
        };
      }

      return {
        valid: true,
        error: `Validation endpoint returned HTTP ${res.status}${errorDetail ? `: ${errorDetail}` : ""}, treating PAT as valid`,
        unsupported: false,
      };
    }

    return {
      valid: false,
      error: `Qoder API returned HTTP ${res.status}${errorDetail ? `: ${errorDetail}` : ""}`,
      unsupported: false,
    };
  } catch (e: any) {
    return {
      valid: false,
      error: `Qoder validation request failed: ${e.message}`,
      unsupported: false,
    };
  }
}
