import { writeFile, chmod } from "node:fs/promises";
import type { McpToolCaller } from "./mcp-tool-client.ts";

export interface RingCentralSipDiscoveryOptions {
  admin: McpToolCaller;
  extensionId?: string;
  extensionNamePattern?: RegExp;
  deviceId?: string;
  writeEnvPath?: string;
}

export interface RingCentralSipDeviceCandidate {
  extensionId: string;
  extensionNumber?: string;
  extensionName?: string;
  deviceId: string;
  deviceName?: string;
  deviceStatus?: string;
  deviceType?: string;
}

export interface RingCentralSipEnv {
  RINGCENTRAL_SIP_DOMAIN: string;
  RINGCENTRAL_SIP_OUTBOUND_PROXY: string;
  RINGCENTRAL_SIP_USERNAME: string;
  RINGCENTRAL_SIP_PASSWORD: string;
  RINGCENTRAL_SIP_AUTHORIZATION_ID: string;
  RINGCENTRAL_SIP_CODEC: "OPUS/16000";
}

export interface RingCentralSipDiscoveryResult {
  ok: boolean;
  reason?: string;
  checkedExtensions: number;
  matchingExtensions: Array<{
    id: string;
    extensionNumber?: string;
    name?: string;
    type?: string;
    status?: string;
  }>;
  otherPhoneCandidates: RingCentralSipDeviceCandidate[];
  sipEnv?: RingCentralSipEnv;
  envWrittenTo?: string;
}

export function describeSipDiscoveryResult(result: RingCentralSipDiscoveryResult): string {
  if (result.ok) {
    return `found ${result.otherPhoneCandidates.length} Other Phone candidate(s) across ${result.matchingExtensions.length} matching extension(s)`;
  }
  if (result.matchingExtensions.length === 0) {
    return "No enabled AI Scheduling Backup extension matched. Create extension 9001, then add an Existing Phone / Other Phone device.";
  }
  const extensionList = result.matchingExtensions
    .map((extension) => `${extension.extensionNumber ?? extension.id} ${extension.name ?? ""}`.trim())
    .join(", ");
  return `${result.reason ?? "No usable SIP device found"} Matching extension(s): ${extensionList}.`;
}

export async function discoverRingCentralSipDevice(
  opts: RingCentralSipDiscoveryOptions,
): Promise<RingCentralSipDiscoveryResult> {
  const extensions = await matchingExtensions(opts);
  const candidates: RingCentralSipDeviceCandidate[] = [];

  for (const extension of extensions) {
    const devices = (await opts.admin.callTool("list_extension_devices", {
      extension_id: extension.id,
    })) as { records?: Array<Record<string, unknown>> };
    for (const device of devices.records ?? []) {
      if (String(device.type ?? "") === "Other Phone") {
        candidates.push({
          extensionId: extension.id,
          extensionNumber: extension.extensionNumber,
          extensionName: extension.name,
          deviceId: String(device.id),
          deviceName: optionalString(device.name),
          deviceStatus: optionalString(device.status),
          deviceType: optionalString(device.type),
        });
      }
    }
  }

  const selectedDeviceId = opts.deviceId ?? candidates[0]?.deviceId;
  const result: RingCentralSipDiscoveryResult = {
    ok: Boolean(selectedDeviceId),
    checkedExtensions: extensions.length,
    matchingExtensions: extensions,
    otherPhoneCandidates: candidates,
    ...(selectedDeviceId ? {} : { reason: "No Other Phone device found for the selected extension(s)." }),
  };

  if (!selectedDeviceId) return result;

  const rawSipInfo = await opts.admin.callTool("read_device_sip_info", {
    device_id: selectedDeviceId,
  });
  result.sipEnv = sipEnvFromRingCentral(rawSipInfo);

  if (opts.writeEnvPath) {
    await writeSipEnvFile(opts.writeEnvPath, result.sipEnv);
    result.envWrittenTo = opts.writeEnvPath;
  }

  return result;
}

export function sipEnvFromRingCentral(raw: unknown): RingCentralSipEnv {
  const info = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
  const proxy = outboundProxyFromSipInfo(info);
  return {
    RINGCENTRAL_SIP_DOMAIN: requiredString(info.domain, "domain"),
    RINGCENTRAL_SIP_OUTBOUND_PROXY: proxy,
    RINGCENTRAL_SIP_USERNAME: requiredString(info.userName ?? info.username, "userName"),
    RINGCENTRAL_SIP_PASSWORD: requiredString(info.password, "password"),
    RINGCENTRAL_SIP_AUTHORIZATION_ID: requiredString(info.authorizationId, "authorizationId"),
    RINGCENTRAL_SIP_CODEC: "OPUS/16000",
  };
}

export function redactSipEnv(env: RingCentralSipEnv): Record<string, string> {
  return Object.fromEntries(
    Object.entries(env).map(([key, value]) => [
      key,
      key.includes("PASSWORD") ? `<redacted:${value.length}>` : value,
    ]),
  );
}

async function matchingExtensions(opts: RingCentralSipDiscoveryOptions): Promise<RingCentralSipDiscoveryResult["matchingExtensions"]> {
  const all = (await opts.admin.callTool("list_extensions")) as {
    records?: Array<Record<string, unknown>>;
  };
  const pattern = opts.extensionNamePattern ?? /AI Scheduling Backup/i;
  return (all.records ?? [])
    .map((extension) => ({
      id: String(extension.id),
      extensionNumber: optionalString(extension.extensionNumber),
      name: optionalString(extension.name),
      type: optionalString(extension.type),
      status: optionalString(extension.status),
    }))
    .filter((extension) => {
      if (opts.extensionId) return extension.id === opts.extensionId;
      return (
        extension.status === "Enabled" &&
        pattern.test(`${extension.name ?? ""} ${extension.extensionNumber ?? ""}`)
      );
    });
}

function outboundProxyFromSipInfo(info: Record<string, unknown>): string {
  const proxies = Array.isArray(info.outboundProxies)
    ? (info.outboundProxies as Array<Record<string, unknown>>)
    : [];
  const naProxy = proxies.find((proxy) => proxy.region === "NA") ?? proxies[0];
  if (!naProxy) return requiredString(info.proxyTLS ?? info.proxy, "outbound proxy");
  return requiredString(
    naProxy.proxyTLS ?? naProxy.proxy ?? naProxy.host ?? naProxy.address,
    "outbound proxy",
  );
}

async function writeSipEnvFile(path: string, env: RingCentralSipEnv): Promise<void> {
  const body = `${Object.entries(env)
    .map(([key, value]) => `${key}=${shellQuote(value)}`)
    .join("\n")}\n`;
  await writeFile(path, body, { mode: 0o600 });
  await chmod(path, 0o600);
}

function shellQuote(value: string): string {
  return `'${value.replace(/'/g, "'\\''")}'`;
}

function requiredString(value: unknown, name: string): string {
  if (typeof value !== "string" || !value.trim()) {
    throw new Error(`SIP info missing ${name}`);
  }
  return value.trim();
}

function optionalString(value: unknown): string | undefined {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
