import { AuthSessionDO } from "./auth_session_do";
import { createCode } from "./code";
import { renderOAuthPage, renderApiKeyForm, renderAddonUrlForm } from "./auth_page";
import { handleSmokeTestRun, handleSmokeTestStatus } from "./smoke-test";

export { AuthSessionDO };

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

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

function json(d: unknown, s = 200): Response {
  return new Response(JSON.stringify(d), { status: s, headers: { ...CORS, "Content-Type": "application/json" } });
}
function html(c: string, s = 200): Response {
  return new Response(c, { status: s, headers: { ...CORS, "Content-Type": "text/html; charset=utf-8" } });
}

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 });
    try {
      // API alias: POST /api/debrid/auth-sessions
      if (url.pathname === "/api/debrid/auth-sessions" && request.method === "POST") return handleApiAuthCreate(request, env);
      // API alias: 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);
      // API alias: 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);
      // OAuth start: POST /debrid/oauth/start/{service
      const oa = url.pathname.match(/^\/debrid\/oauth\/start\/([a-z]+)$/);
      if (oa && request.method === "GET") return handleOAuthStart(oa[1], env);

      // Key start: POST /debrid/key/start/{service}
      const ks = url.pathname.match(/^\/debrid\/key\/start\/([a-z]+)$/);
      if (ks && request.method === "GET") return handleKeyStart(ks[1], env);

      // Redirect: GET /debrid/auth/{code} (Android buildAuthUrl)
      const rDebridAuth = url.pathname.match(/^\/debrid\/auth\/([a-z0-9]+)$/);
      if (rDebridAuth && request.method === "GET") return handleAuthForm(rDebridAuth[1], env);
      // Form: 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);

      // Key submit: 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);
      }

      // Status: 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);

      // Cancel: 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);

      // URL start: GET /addon/url/start
      if (url.pathname === "/api/addons/url-sessions" && (request.method === "POST" || request.method === "GET")) return handleUrlStart(env);
      // API alias: GET/POST /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);
      // API alias: 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);
      //
      if (url.pathname === "/addon/url/start" && request.method === "GET") return handleUrlStart(env);

      // Redirect: GET /addons/url/{code} (Android buildUrlEntryUrl)
      const rAddonUrl = url.pathname.match(/^\/addons\/url\/([a-z0-9]+)$/);
      if (rAddonUrl && request.method === "GET") return handleUrlForm(rAddonUrl[1], env);
      // URL form: 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);

      // URL submit: 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);
      }

      // URL status: 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);

      // URL cancel: 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: POST /api/v1/smoke-test/run
      if (url.pathname === "/api/v1/smoke-test/run" && request.method === "POST") return handleSmokeTestRun(request);
      // Smoke test: 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: (e as Error).message }, 500); }
  },
};

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

async function handleApiAuthCreate(request: Request, env: Env): Promise<Response> {
  try {
    const body = await request.json() as any;
    const providerId = body.providerId || "";
    const oauthServices = ["realdebrid", "premiumize"];
    const keyServices = ["torbox", "alldebrid", "debrider", "offcloud"];
    if (oauthServices.includes(providerId)) return handleOAuthStart(providerId, env);
    if (keyServices.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 (!["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;
  if (s.authType === "oauth")
    return html(renderOAuthPage({ code, service: s.service, userCode: s.userCode || "",
      verificationUrl: s.verificationUrl || "", status: s.status,
      appName: env.APP_NAME || "Unspooled", publicHost: env.PUBLIC_HOST, error: s.error }));
  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 });
  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);
  // If OAuth, also poll
  let s = await r.json() as any;
  if (s.authType === "oauth" && s.status === "active") {
    const r2 = await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/auth/poll"));
    s = await r2.json() as any;
  }
  return json({ code: s.code, service: s.service, authType: s.authType, status: s.status, userCode: s.userCode });
}

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" });
}

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" });
}
