import type { Response } from "express";

import { generateToolCallId, generateFakeData } from "../fake-data.js";
import type {
  ResponseCreateRequest,
  ResponseObject,
  ResponseOutputItem,
  ResponseOutputContent,
  ServerConfig,
} from "../types.js";

/**
 * Generate a response ID
 */
function generateResponseId(): string {
  const chars =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  let id = "resp_";
  for (let i = 0; i < 24; i++) {
    id += chars[Math.floor(Math.random() * chars.length)];
  }
  return id;
}

/**
 * Generate an output item ID
 */
function generateItemId(): string {
  const chars =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  let id = "item_";
  for (let i = 0; i < 24; i++) {
    id += chars[Math.floor(Math.random() * chars.length)];
  }
  return id;
}

/**
 * Estimate tokens from text
 */
function estimateTokens(text: string): number {
  return Math.ceil(text.length / 4);
}

/**
 * Extract input text from request
 */
function getInputText(req: ResponseCreateRequest): string {
  if (typeof req.input === "string") {
    return req.input;
  }
  if (Array.isArray(req.input)) {
    return req.input
      .map((item) => {
        if (typeof item.content === "string") {
          return item.content;
        }
        if (Array.isArray(item.content)) {
          return item.content
            .filter((c) => c.type === "input_text")
            .map((c) => c.text || "")
            .join(" ");
        }
        return "";
      })
      .join(" ");
  }
  return "";
}

/**
 * Handle non-streaming response
 */
export async function handleNonStreaming(
  req: ResponseCreateRequest,
  config: ServerConfig
): Promise<ResponseObject> {
  const id = generateResponseId();
  const createdAt = Date.now() / 1000;

  // Decide whether to make a tool call
  // toolCallProbability overrides client's tool_choice (except "none" which always disables)
  const shouldMakeToolCall =
    req.tools &&
    req.tools.length > 0 &&
    req.tool_choice !== "none" &&
    Math.random() < config.toolCallProbability;

  const output: ResponseOutputItem[] = [];

  if (shouldMakeToolCall && req.tools) {
    // Generate a function call
    const tool = req.tools[Math.floor(Math.random() * req.tools.length)];

    // Guard: ensure tool has valid function name
    if (tool?.function?.name) {
      const args = await generateFakeData(tool.function.parameters);

      output.push({
        type: "function_call",
        id: generateItemId(),
        status: "completed",
        call_id: generateToolCallId(),
        name: tool.function.name,
        arguments: JSON.stringify(args),
      });
    } else {
      // Fallback to text response if tool is invalid
      const content: ResponseOutputContent = {
        type: "output_text",
        text: config.getDefaultResponse(),
        annotations: [],
      };

      output.push({
        type: "message",
        id: generateItemId(),
        status: "completed",
        role: "assistant",
        content: [content],
      });
    }
  } else {
    // Generate a message
    const content: ResponseOutputContent = {
      type: "output_text",
      text: config.getDefaultResponse(),
      annotations: [],
    };

    output.push({
      type: "message",
      id: generateItemId(),
      status: "completed",
      role: "assistant",
      content: [content],
    });
  }

  const inputText = getInputText(req);
  const outputText = output
    .map((o) =>
      o.type === "message"
        ? o.content?.map((c) => c.text).join("") || ""
        : o.arguments || ""
    )
    .join("");

  return {
    id,
    object: "response",
    created_at: createdAt,
    model: req.model,
    status: "completed",
    output,
    usage: {
      input_tokens: estimateTokens(inputText),
      output_tokens: estimateTokens(outputText),
      total_tokens: estimateTokens(inputText) + estimateTokens(outputText),
    },
    metadata: req.metadata || {},
    error: null,
    incomplete_details: null,
  };
}

/**
 * Handle streaming response
 */
export async function handleStreaming(
  req: ResponseCreateRequest,
  res: Response,
  config: ServerConfig
): Promise<void> {
  const id = generateResponseId();
  const createdAt = Date.now() / 1000;

  // Set SSE headers
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");
  res.setHeader("X-Accel-Buffering", "no");

  const sendEvent = (event: { type: string; [key: string]: unknown }) => {
    res.write(`event: ${event.type}\n`);
    res.write(`data: ${JSON.stringify(event)}\n\n`);
  };

  // Decide whether to make a tool call
  // toolCallProbability overrides client's tool_choice (except "none" which always disables)
  const shouldMakeToolCall =
    req.tools &&
    req.tools.length > 0 &&
    req.tool_choice !== "none" &&
    Math.random() < config.toolCallProbability;

  // Create initial response object
  const initialResponse: ResponseObject = {
    id,
    object: "response",
    created_at: createdAt,
    model: req.model,
    status: "in_progress",
    output: [],
    metadata: req.metadata || {},
    error: null,
    incomplete_details: null,
  };

  // Initial delay (time to first token)
  await sleep(config.streamInitialDelayMs);

  // Send response.created
  sendEvent({
    type: "response.created",
    response: initialResponse,
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Send response.in_progress
  sendEvent({
    type: "response.in_progress",
    response: initialResponse,
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  if (shouldMakeToolCall && req.tools) {
    await streamFunctionCall(req, res, id, createdAt, config, sendEvent);
  } else {
    await streamTextContent(req, res, id, createdAt, config, sendEvent);
  }

  // Calculate usage
  const inputText = getInputText(req);
  const inputTokens = estimateTokens(inputText);
  const outputTokens = estimateTokens(config.getDefaultResponse());

  // Send response.completed
  const completedResponse: ResponseObject = {
    id,
    object: "response",
    created_at: createdAt,
    model: req.model,
    status: "completed",
    output: [], // Would need to track this
    usage: {
      input_tokens: inputTokens,
      output_tokens: outputTokens,
      total_tokens: inputTokens + outputTokens,
    },
    metadata: req.metadata || {},
    error: null,
    incomplete_details: null,
  };

  sendEvent({
    type: "response.completed",
    response: completedResponse,
  });

  res.end();
}

async function streamTextContent(
  req: ResponseCreateRequest,
  res: Response,
  responseId: string,
  createdAt: number,
  config: ServerConfig,
  sendEvent: (event: { type: string; [key: string]: unknown }) => void
): Promise<void> {
  const content = config.getDefaultResponse();
  const itemId = generateItemId();

  // Send output_item.added
  const outputItem: ResponseOutputItem = {
    type: "message",
    id: itemId,
    status: "in_progress",
    role: "assistant",
    content: [],
  };

  sendEvent({
    type: "response.output_item.added",
    output_index: 0,
    item: outputItem,
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Send content_part.added
  sendEvent({
    type: "response.content_part.added",
    output_index: 0,
    content_index: 0,
    part: {
      type: "output_text",
      text: "",
      annotations: [],
    },
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Stream text deltas
  for (let i = 0; i < content.length; i += config.streamChunkSize) {
    const chunk = content.slice(i, i + config.streamChunkSize);

    sendEvent({
      type: "response.output_text.delta",
      output_index: 0,
      content_index: 0,
      delta: chunk,
    });

    await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);
  }

  // Send text done
  sendEvent({
    type: "response.output_text.done",
    output_index: 0,
    content_index: 0,
    text: content,
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Send content_part.done
  sendEvent({
    type: "response.content_part.done",
    output_index: 0,
    content_index: 0,
    part: {
      type: "output_text",
      text: content,
      annotations: [],
    },
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Send output_item.done
  sendEvent({
    type: "response.output_item.done",
    output_index: 0,
    item: {
      type: "message",
      id: itemId,
      status: "completed",
      role: "assistant",
      content: [
        {
          type: "output_text",
          text: content,
          annotations: [],
        },
      ],
    },
  });
}

async function streamFunctionCall(
  req: ResponseCreateRequest,
  res: Response,
  responseId: string,
  createdAt: number,
  config: ServerConfig,
  sendEvent: (event: { type: string; [key: string]: unknown }) => void
): Promise<void> {
  const tool = req.tools![Math.floor(Math.random() * req.tools!.length)];

  // Guard: if tool is invalid, fall back to text streaming
  if (!tool?.function?.name) {
    await streamTextContent(req, res, responseId, createdAt, config, sendEvent);
    return;
  }

  const args = JSON.stringify(await generateFakeData(tool.function.parameters));
  const itemId = generateItemId();
  const callId = generateToolCallId();

  // Send output_item.added
  const outputItem: ResponseOutputItem = {
    type: "function_call",
    id: itemId,
    status: "in_progress",
    call_id: callId,
    name: tool.function.name,
    arguments: "",
  };

  sendEvent({
    type: "response.output_item.added",
    output_index: 0,
    item: outputItem,
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Stream arguments deltas
  for (let i = 0; i < args.length; i += config.streamChunkSize) {
    const chunk = args.slice(i, i + config.streamChunkSize);

    sendEvent({
      type: "response.function_call_arguments.delta",
      output_index: 0,
      call_id: callId,
      delta: chunk,
    });

    await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);
  }

  // Send arguments done
  sendEvent({
    type: "response.function_call_arguments.done",
    output_index: 0,
    call_id: callId,
    arguments: args,
  });

  await sleepWithJitter(config.streamDelayMs, config.streamJitterMs);

  // Send output_item.done
  sendEvent({
    type: "response.output_item.done",
    output_index: 0,
    item: {
      type: "function_call",
      id: itemId,
      status: "completed",
      call_id: callId,
      name: tool.function.name,
      arguments: args,
    },
  });
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function sleepWithJitter(baseMs: number, jitterMs: number): Promise<void> {
  const jitter = Math.random() * jitterMs;
  return sleep(baseMs + jitter);
}
