import { ConfigSessionDO } from "./do";
import type { ConfigSession, SessionStatusResponse } from "./models";
import { createCode } from "./code";
import { renderConfigPage } from "./page";
import { AuthSessionDO } from "./auth_session_do";
import { renderOAuthPage, renderApiKeyForm, renderAddonUrlForm } from "./auth_page";
import { handleSmokeTestRun, handleSmokeTestStatus } from "./smoke-test";

export { ConfigSessionDO, AuthSessionDO };

export interface Env {
  ADDON_CONFIG_SESSIONS: DurableObjectNamespace;
  AUTH_SESSIONS: DurableObjectNamespace;
  PUBLIC_HOST: string;
  APP_NAME?: string;
}

const CORS_HEADERS = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
};

function json(data: unknown, status = 200): Response {
  return new Response(JSON.stringify(data), {
    status, headers: { ...CORS_HEADERS, "Content-Type": "application/json" },
  });
}

function html(content: string, status = 200): Response {
  return new Response(content, {
    status, headers: { ...CORS_HEADERS, "Content-Type": "text/html; charset=utf-8" },
  });
}

function sessionToResponse(s: ConfigSession): SessionStatusResponse {
  return {
    code: s.code, addonId: s.addonId, addonName: s.addonName,
    baseManifestUrl: s.baseManifestUrl, configureUrl: s.configureUrl,
    status: s.status, configuredManifestUrl: s.configuredManifestUrl,
    validatedManifestOk: s.validatedManifestOk, createdAt: s.createdAt, expiresAt: s.expiresAt,
  };
}

function doFetch(code: string, suffix: string, env: Env, req?: Request): Promise<Response> {
  const id = env.ADDON_CONFIG_SESSIONS.idFromName(code);
  const stub = env.ADDON_CONFIG_SESSIONS.get(id);
  const url = env.PUBLIC_HOST + suffix;
  return stub.fetch(req || new Request(url));
}

async function getStub(code: string, prefix: string, env: Env) {
  const id = env.AUTH_SESSIONS.idFromName(prefix + code);
  return env.AUTH_SESSIONS.get(id);
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    if (request.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS_HEADERS });

    try {
      // ── Config Session Routes ──

      // POST /api/addons/config-sessions
      if (url.pathname === "/api/addons/config-sessions" && request.method === "POST") {
        return handleCreateSession(request, env);
      }
      // GET /api/addons/config-sessions/{code}
      const apiStatus = url.pathname.match(/^\/api\/addons\/config-sessions\/([a-z0-9]+)$/);
      if (apiStatus && request.method === "GET") return handleGetSession(apiStatus[1], env);
      // POST .../complete
      const apiC = url.pathname.match(/^\/api\/addons\/config-sessions\/([a-z0-9]+)\/complete$/);
      if (apiC && request.method === "POST") return handleCompleteSession(apiC[1], request, env);
      // POST .../cancel
      const apiX = url.pathname.match(/^\/api\/addons\/config-sessions\/([a-z0-9]+)\/cancel$/);
      if (apiX && request.method === "POST") return handleCancelSession(apiX[1], env);

      // GET /addons/config/{code}
      const page = url.pathname.match(/^\/addons\/config\/([a-z0-9]+)$/);
      if (page && request.method === "GET") return handleConfigPage(page[1], env);
      // GET /addons/config/{code}/status
      const ps = url.pathname.match(/^\/addons\/config\/([a-z0-9]+)\/status$/);
      if (ps && request.method === "GET") return handleGetSession(ps[1], env);
      // POST /addons/config/{code}/complete
      const pc = url.pathname.match(/^\/addons\/config\/([a-z0-9]+)\/complete$/);
      if (pc && request.method === "POST") return handleCompleteSession(pc[1], request, env);
      // POST /addons/config/{code}/cancel
      const px = url.pathname.match(/^\/addons\/config\/([a-z0-9]+)\/cancel$/);
      if (px && request.method === "POST") return handleCancelSession(px[1], env);

      // ── Debrid Auth Routes ──

      // POST /api/debrid/auth-sessions
      if (url.pathname === "/api/debrid/auth-sessions" && request.method === "POST") return handleApiAuthCreate(request, env);
      // GET /api/debrid/auth-sessions/{code}
      const da = url.pathname.match(/^\/api\/debrid\/auth-sessions\/([a-z0-9]+)$/);
      if (da && request.method === "GET") return handleAuthStatus(da[1], env);
      // POST /api/debrid/auth-sessions/{code}/cancel
      const dx = url.pathname.match(/^\/api\/debrid\/auth-sessions\/([a-z0-9]+)\/cancel$/);
      if (dx && request.method === "POST") return handleAuthCancel(dx[1], env);

      // GET /debrid/oauth/start/{service}
      const oa = url.pathname.match(/^\/debrid\/oauth\/start\/([a-z]+)$/);
      if (oa && request.method === "GET") return handleOAuthStart(oa[1], env);
      // GET /debrid/key/start/{service}
      const ks = url.pathname.match(/^\/debrid\/key\/start\/([a-z]+)$/);
      if (ks && request.method === "GET") return handleKeyStart(ks[1], env);
      // GET /debrid/auth/{code}
      const rDebridAuth = url.pathname.match(/^\/debrid\/auth\/([a-z0-9]+)$/);
      if (rDebridAuth && request.method === "GET") return handleAuthForm(rDebridAuth[1], env);
      // GET /debrid/auth/form/{code}
      const af = url.pathname.match(/^\/debrid\/auth\/form\/([a-z0-9]+)$/);
      if (af && request.method === "GET") return handleAuthForm(af[1], env);
      // POST /debrid/auth/submit/{code}
      const as = url.pathname.match(/^\/debrid\/auth\/submit\/([a-z0-9]+)$/);
      if (as && request.method === "POST") {
        const b = await request.json() as any;
        return handleAuthSubmit(as[1], b.apiKey || "", env);
      }
      // GET /debrid/auth/status/{code}
      const ast = url.pathname.match(/^\/debrid\/auth\/status\/([a-z0-9]+)$/);
      if (ast && request.method === "GET") return handleAuthStatus(ast[1], env);
      // GET /debrid/auth/cancel/{code}
      const ac = url.pathname.match(/^\/debrid\/auth\/cancel\/([a-z0-9]+)$/);
      if (ac && request.method === "GET") return handleAuthCancel(ac[1], env);

      // ── Addon URL Routes ──

      // POST /api/addons/url-sessions
      if (url.pathname === "/api/addons/url-sessions" && (request.method === "POST" || request.method === "GET")) return handleUrlStart(env);
      // GET /api/addons/url-sessions/{code}
      const ua = url.pathname.match(/^\/api\/addons\/url-sessions\/([a-z0-9]+)$/);
      if (ua && request.method === "GET") return handleUrlStatus(ua[1], env);
      // POST /api/addons/url-sessions/{code}/cancel
      const ux = url.pathname.match(/^\/api\/addons\/url-sessions\/([a-z0-9]+)\/cancel$/);
      if (ux && request.method === "POST") return handleUrlCancel(ux[1], env);
      // GET /addon/url/start
      if (url.pathname === "/addon/url/start" && request.method === "GET") return handleUrlStart(env);
      // GET /addons/url/{code}
      const rAddonUrl = url.pathname.match(/^\/addons\/url\/([a-z0-9]+)$/);
      if (rAddonUrl && request.method === "GET") return handleUrlForm(rAddonUrl[1], env);
      // GET /addon/url/form/{code}
      const uf = url.pathname.match(/^\/addon\/url\/form\/([a-z0-9]+)$/);
      if (uf && request.method === "GET") return handleUrlForm(uf[1], env);
      // POST /addon/url/submit/{code}
      const us = url.pathname.match(/^\/addon\/url\/submit\/([a-z0-9]+)$/);
      if (us && request.method === "POST") {
        const b = await request.json() as any;
        return handleUrlSubmit(us[1], b.manifestUrl || "", env);
      }
      // GET /addon/url/status/{code}
      const ust = url.pathname.match(/^\/addon\/url\/status\/([a-z0-9]+)$/);
      if (ust && request.method === "GET") return handleUrlStatus(ust[1], env);
      // GET /addon/url/cancel/{code}
      const uc = url.pathname.match(/^\/addon\/url\/cancel\/([a-z0-9]+)$/);
      if (uc && request.method === "GET") return handleUrlCancel(uc[1], env);

      // ── Smoke Test Routes ──

      // POST /api/v1/smoke-test/run
      if (url.pathname === "/api/v1/smoke-test/run" && request.method === "POST") return handleSmokeTestRun(request);
      // GET /api/v1/smoke-test/status
      if (url.pathname === "/api/v1/smoke-test/status" && request.method === "GET") return handleSmokeTestStatus();

      return json({ error: "Not found" }, 404);
    } catch (e) {
      return json({ error: "Internal error", message: (e as Error).message }, 500);
    }
  },
};

// ═══════════════════════════════════════
//  Config Session Handlers
// ═══════════════════════════════════════

async function handleCreateSession(request: Request, env: Env): Promise<Response> {
  const body = await request.json() as any;
  if (!body.addonId || !body.addonName || !body.baseManifestUrl)
    return json({ error: "Missing fields" }, 400);

  const code = createCode();
  const r = await doFetch(code, "/create", env, new Request(env.PUBLIC_HOST + "/create", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ code, addonId: body.addonId, addonName: body.addonName,
      baseManifestUrl: body.baseManifestUrl, configureUrl: body.configureUrl }),
  }));
  const session = await r.json() as any;
  return json({ ...sessionToResponse(session), configUrl: env.PUBLIC_HOST + "/addons/config/" + code }, 201);
}

async function handleGetSession(code: string, env: Env): Promise<Response> {
  const r = await doFetch(code, "/status", env);
  if (r.status === 404) return json({ error: "Not found", status: "cancelled" }, 404);
  const session = await r.json() as ConfigSession;
  return json(sessionToResponse(session));
}

async function handleCompleteSession(code: string, request: Request, env: Env): Promise<Response> {
  const body = await request.json() as any;
  if (!body.configuredManifestUrl) return json({ error: "Missing configuredManifestUrl" }, 400);

  const r = await doFetch(code, "/complete", env, new Request(env.PUBLIC_HOST + "/complete", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ configuredManifestUrl: body.configuredManifestUrl }),
  }));
  const session = await r.json() as any;
  if (!session.validatedManifestOk)
    return json({ ...sessionToResponse(session), error: session.validationError || "Validation failed" }, 400);
  return json(sessionToResponse(session));
}

async function handleCancelSession(code: string, env: Env): Promise<Response> {
  await doFetch(code, "/cancel", env);
  return json({ status: "cancelled" });
}

async function handleConfigPage(code: string, env: Env): Promise<Response> {
  const r = await doFetch(code, "/status", env);
  const session = await r.json() as any;
  if (session && session.status === "pending") {
    await doFetch(code, "/activate", env);
    session.status = "active";
  }
  if (!session || r.status === 404)
    return html(renderConfigPage({ code, addonName: "Unknown", configureUrl: null, status: "cancelled",
      publicHost: env.PUBLIC_HOST, appName: env.APP_NAME || "Unspooled", error: "Not found" }), 404);
  return html(renderConfigPage({ code, addonName: session.addonName, configureUrl: session.configureUrl,
    status: session.status, publicHost: env.PUBLIC_HOST, appName: env.APP_NAME || "Unspooled",
    error: session.validationError }));
}

// ═══════════════════════════════════════
//  Debrid Auth Handlers
// ═══════════════════════════════════════

async function handleApiAuthCreate(request: Request, env: Env): Promise<Response> {
  try {
    const body = await request.json() as any;
    const providerId = (body.providerId || "").replace(/_/g, "").toLowerCase();
    // All providers use API key entry — no OAuth device code flow
    const allProviders = ["realdebrid", "premiumize", "torbox", "alldebrid", "debrider", "offcloud"];
    if (allProviders.includes(providerId)) return handleKeyStart(providerId, env);
    return json({ error: "Unknown provider: " + providerId }, 400);
  } catch { return json({ error: "Invalid JSON" }, 400); }
}

async function handleOAuthStart(service: string, env: Env) {
  if (!["realdebrid","premiumize"].includes(service)) return json({ error: "Unsupported" }, 400);
  const code = createCode();
  const stub = await getStub(code, "debrid-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/oauth/start?service=" + service));
  const s = await r.json() as any;
  return json({ code, service: s.service, authType: "oauth", status: s.status,
    userCode: s.userCode, verificationUrl: s.verificationUrl, interval: s.interval,
    formUrl: env.PUBLIC_HOST + "/debrid/auth/form/" + code,
    createdAt: s.createdAt, expiresAt: s.expiresAt }, 201);
}

async function handleKeyStart(service: string, env: Env) {
  if (!["realdebrid","premiumize","torbox","alldebrid","debrider","offcloud"].includes(service)) return json({ error: "Unsupported" }, 400);
  const code = createCode();
  const stub = await getStub(code, "debrid-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/key/start?service=" + service));
  const s = await r.json() as any;
  return json({ code, service: s.service, authType: "apikey", status: s.status,
    formUrl: env.PUBLIC_HOST + "/debrid/auth/form/" + code,
    createdAt: s.createdAt, expiresAt: s.expiresAt }, 201);
}

async function handleAuthForm(code: string, env: Env) {
  const stub = await getStub(code, "debrid-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/auth/status"));
  if (r.status === 404) return html(renderApiKeyForm({ code, service: "unknown", authType: "apikey",
    status: "cancelled", appName: env.APP_NAME || "Unspooled", publicHost: env.PUBLIC_HOST,
    error: "Session not found" }), 404);
  const s = await r.json() as any;
  // Always show API key form regardless of authType
  return html(renderApiKeyForm({ code, service: s.service, authType: "apikey", status: s.status,
    appName: env.APP_NAME || "Unspooled", publicHost: env.PUBLIC_HOST, error: s.error }));
}

async function handleAuthSubmit(code: string, apiKey: string, env: Env) {
  if (!apiKey) return json({ error: "Missing apiKey" }, 400);
  const stub = await getStub(code, "debrid-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/auth/submit", {
    method: "POST", body: JSON.stringify({ apiKey }), headers: { "Content-Type": "application/json" },
  }));
  const s = await r.json() as any;
  if (s.status === "completed") return json({ code, service: s.service, authType: s.authType, status: s.status, configuredApiKey: s.apiKey });
  return json({ code, service: s.service, authType: s.authType, status: s.status, error: s.error || "Failed" }, 400);
}

async function handleAuthStatus(code: string, env: Env) {
  const stub = await getStub(code, "debrid-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/auth/status"));
  if (r.status === 404) return json({ error: "Not found", status: "cancelled" }, 404);
  const s = await r.json() as any;
  return json({
    code: s.code, service: s.service, authType: s.authType, status: s.status,
    userCode: s.userCode, configuredApiKey: s.apiKey || s.token || null, error: s.error || null,
  });
}

async function handleAuthCancel(code: string, env: Env) {
  const stub = await getStub(code, "debrid-", env);
  await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/auth/cancel"));
  return json({ status: "cancelled" });
}

// ═══════════════════════════════════════
//  Addon URL Handlers
// ═══════════════════════════════════════

async function handleUrlStart(env: Env) {
  const code = createCode();
  const stub = await getStub(code, "url-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/addon/url/create"));
  const s = await r.json() as any;
  return json({ code, status: s.status, formUrl: env.PUBLIC_HOST + "/addon/url/form/" + code,
    createdAt: s.createdAt, expiresAt: s.expiresAt }, 201);
}

async function handleUrlForm(code: string, env: Env) {
  const stub = await getStub(code, "url-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/addon/url/status"));
  if (r.status === 404) return html(renderAddonUrlForm({ code, status: "cancelled",
    appName: env.APP_NAME || "Unspooled", publicHost: env.PUBLIC_HOST, error: "Not found" }), 404);
  const s = await r.json() as any;
  return html(renderAddonUrlForm({ code, status: s.status, manifestUrl: s.manifestUrl,
    validatedManifestOk: s.validatedManifestOk, manifestName: s.manifestName,
    appName: env.APP_NAME || "Unspooled", publicHost: env.PUBLIC_HOST, error: s.error }));
}

async function handleUrlSubmit(code: string, manifestUrl: string, env: Env) {
  if (!manifestUrl) return json({ error: "Missing manifestUrl" }, 400);
  try { new URL(manifestUrl); } catch { return json({ error: "Invalid URL" }, 400); }
  const stub = await getStub(code, "url-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/addon/url/submit", {
    method: "POST", body: JSON.stringify({ manifestUrl }), headers: { "Content-Type": "application/json" },
  }));
  const s = await r.json() as any;
  if (s.validatedManifestOk) return json({ code, status: s.status, manifestUrl: s.manifestUrl,
    manifestName: s.manifestName, validatedManifestOk: true });
  return json({ code, status: s.status, validatedManifestOk: false, error: s.error || "Failed" }, 400);
}

async function handleUrlStatus(code: string, env: Env) {
  const stub = await getStub(code, "url-", env);
  const r = await stub.fetch(new Request(env.PUBLIC_HOST + "/addon/url/status"));
  if (r.status === 404) return json({ error: "Not found", status: "cancelled" }, 404);
  const s = await r.json() as any;
  return json({ code: s.code, status: s.status, manifestUrl: s.manifestUrl,
    manifestName: s.manifestName, validatedManifestOk: s.validatedManifestOk, error: s.error });
}

async function handleUrlCancel(code: string, env: Env) {
  const stub = await getStub(code, "url-", env);
  await stub.fetch(new Request(env.PUBLIC_HOST + "/addon/url/cancel"));
  return json({ status: "cancelled" });
}
