// ── Auth Session DO — handles all auth/addon-url logic via fetch() dispatch ──
import { createCode } from "./code";

const SESSION_TTL_MS = 15 * 60 * 1000;
const MAX_POLLS = 300;

const RD_CLIENT_ID = "X245J4A8I7W40";
const RD_DEVICE_URL = "https://api.real-debrid.com/rest/1.0/oauth/device/code";
const RD_TOKEN_URL = "https://api.real-debrid.com/rest/1.0/oauth/token";
const PM_CLIENT_ID = "unspooled_tv";
const PM_DEVICE_URL = "https://www.premiumize.me/device/code";
const PM_TOKEN_URL = "https://www.premiumize.me/token";

export class AuthSessionDO implements DurableObject {
  private state: DurableObjectState;
  private storage: DurableObjectStorage;

  constructor(ctx: DurableObjectState, _env: unknown) {
    this.state = ctx;
    this.storage = ctx.storage;
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;

    try {
      if (path === "/debrid/oauth/start") {
        const service = url.searchParams.get("service") || "";
        return this.handleOAuthStart(service);
      }
      if (path === "/debrid/key/start") {
        const service = url.searchParams.get("service") || "";
        return this.handleKeyStart(service);
      }
      if (path === "/debrid/auth/submit") {
        const body = await request.json() as { apiKey?: string };
        return this.handleKeySubmit(body.apiKey || "");
      }
      if (path === "/debrid/auth/poll") {
        return this.handleOAuthPoll();
      }
      if (path === "/addon/url/create") {
        return this.handleCreateUrlSession();
      }
      if (path === "/addon/url/submit") {
        const body = await request.json() as { manifestUrl?: string };
        return this.handleUrlSubmit(body.manifestUrl || "");
      }

      // GET handlers
      return this.handleGetStatus(path);
    } catch (e) {
      return new Response(JSON.stringify({ error: (e as Error).message }), {
        status: 500, headers: { "Content-Type": "application/json" },
      });
    }
  }

  private async handleGetStatus(path: string): Promise<Response> {
    if (path.startsWith("/debrid/auth/")) {
      const s = await this.storage.get<any>("auth");
      if (!s) return json({ error: "Not found", status: "cancelled" }, 404);
      if (Date.now() > s.expiresAt && s.status !== "completed") {
        s.status = "cancelled"; await this.storage.put("auth", s);
      }
      return json(s);
    }
    if (path.startsWith("/addon/url/")) {
      const s = await this.storage.get<any>("addonUrl");
      if (!s) return json({ error: "Not found", status: "cancelled" }, 404);
      if (Date.now() > s.expiresAt && s.status !== "completed") {
        s.status = "cancelled"; await this.storage.put("addonUrl", s);
      }
      return json(s);
    }
    return json({ error: "Not found" }, 404);
  }

  private async handleOAuthStart(service: string): Promise<Response> {
    const valid = ["realdebrid", "premiumize"];
    if (!valid.includes(service)) return json({ error: "Unsupported" }, 400);

    try {
      let deviceCode = "", userCode = "", verificationUrl = "", interval = 5;
      const now = Date.now();
      let expiresIn = SESSION_TTL_MS;

      if (service === "realdebrid") {
        const resp = await fetch(RD_DEVICE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: "client_id=" + RD_CLIENT_ID,
        });
        const data = await resp.json() as any;
        deviceCode = data.device_code; userCode = data.user_code;
        verificationUrl = data.verification_url;
        interval = data.interval || 5;
        expiresIn = Math.min(data.expires_in * 1000, SESSION_TTL_MS);
      } else {
        const resp = await fetch(PM_DEVICE_URL, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: "client_id=" + PM_CLIENT_ID,
        });
        const data = await resp.json() as any;
        deviceCode = data.device_code; userCode = data.user_code;
        verificationUrl = data.verification_url;
        interval = data.interval || 5;
        expiresIn = Math.min(data.expires_in * 1000, SESSION_TTL_MS);
      }

      const session = {
        code: this.state.id.toString(), service, authType: "oauth",
        status: "active", deviceCode, userCode, verificationUrl, interval,
        createdAt: now, expiresAt: now + expiresIn, pollCount: 0,
      };
      await this.storage.put("auth", session);
      await this.storage.setAlarm(Date.now() + SESSION_TTL_MS);
      return json(session, 201);
    } catch (e) {
      return json({ error: "OAuth start failed: " + (e as Error).message }, 500);
    }
  }

  private async handleKeyStart(service: string): Promise<Response> {
    const valid = ["realdebrid", "premiumize", "torbox", "alldebrid", "debrider", "offcloud"];
    if (!valid.includes(service)) return json({ error: "Unsupported service: " + service }, 400);

    const now = Date.now();
    const session = {
      code: this.state.id.toString(), service, authType: "apikey",
      status: "pending", createdAt: now, expiresAt: now + SESSION_TTL_MS, pollCount: 0,
    };
    await this.storage.put("auth", session);
    await this.storage.setAlarm(Date.now() + SESSION_TTL_MS);
    return json(session, 201);
  }

  private async handleKeySubmit(apiKey: string): Promise<Response> {
    if (!apiKey) return json({ error: "Missing apiKey" }, 400);
    const s = await this.storage.get<any>("auth");
    if (!s || s.authType !== "apikey") return json({ error: "Not found" }, 404);
    if (s.status !== "pending" && s.status !== "active") return json(s);
    s.status = "completed"; s.apiKey = apiKey; s.token = apiKey;
    await this.storage.put("auth", s);
    return json(s);
  }

  private async handleOAuthPoll(): Promise<Response> {
    const s = await this.storage.get<any>("auth");
    if (!s || s.authType !== "oauth" || s.status !== "active") return json(s || { error: "Not found" }, 404);

    s.pollCount = (s.pollCount || 0) + 1;
    if (s.pollCount > MAX_POLLS) { s.status = "cancelled"; await this.storage.put("auth", s); return json(s); }
    if (Date.now() > s.expiresAt) { s.status = "cancelled"; await this.storage.put("auth", s); return json(s); }

    try {
      const tokenUrl = s.service === "realdebrid" ? RD_TOKEN_URL : PM_TOKEN_URL;
      const clientId = s.service === "realdebrid" ? RD_CLIENT_ID : PM_CLIENT_ID;
      const resp = await fetch(tokenUrl, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: "client_id=" + clientId + "&code=" + s.deviceCode + "&grant_type=http://oauth.net/grant_type/device/1.0",
      });
      if (resp.ok) {
        const data = await resp.json() as any;
        s.status = "completed"; s.token = data.access_token;
      } else if (resp.status === 400) {
        const body = await resp.text();
        if (!body.includes("pending") && !body.includes("slow_down")) {
          s.status = "error"; s.error = "OAuth error: " + resp.status + " " + body;
        }
      } else {
        s.status = "error"; s.error = "HTTP " + resp.status;
      }
    } catch (e) {
      s.status = "error"; s.error = (e as Error).message;
    }
    await this.storage.put("auth", s);
    return json(s);
  }

  private async handleCreateUrlSession(): Promise<Response> {
    const now = Date.now();
    const session = {
      code: this.state.id.toString(), status: "pending",
      createdAt: now, expiresAt: now + SESSION_TTL_MS, pollCount: 0,
    };
    await this.storage.put("addonUrl", session);
    await this.storage.setAlarm(Date.now() + SESSION_TTL_MS);
    return json(session, 201);
  }

  private async handleUrlSubmit(manifestUrl: string): Promise<Response> {
    if (!manifestUrl) return json({ error: "Missing manifestUrl" }, 400);
    try { new URL(manifestUrl); } catch { return json({ error: "Invalid URL" }, 400); }

    const s = await this.storage.get<any>("addonUrl");
    if (!s || (s.status !== "pending" && s.status !== "active"))
      return json({ error: "Not found or already completed" }, 404);

    try {
      const resp = await fetch(manifestUrl, {
        headers: { "User-Agent": "Unspooled-Auth/1.0", Accept: "application/json" },
      });
      if (!resp.ok) {
        s.error = "HTTP " + resp.status; s.validatedManifestOk = false;
      } else {
        const body = await resp.json() as any;
        if (!body.id || !body.name) {
          s.error = "Invalid manifest"; s.validatedManifestOk = false;
        } else {
          s.validatedManifestOk = true; s.manifestName = body.name as string;
        }
      }
    } catch (e) {
      s.error = (e as Error).message;
      s.validatedManifestOk = false;
    }
    s.manifestUrl = manifestUrl;
    s.status = s.validatedManifestOk ? "completed" : "error";
    await this.storage.put("addonUrl", s);
    return json(s);
  }

  async alarm(): Promise<void> {
    for (const key of ["auth", "addonUrl"]) {
      const s = await this.storage.get<any>(key);
      if (s && (s.status === "pending" || s.status === "active")) {
        s.status = "cancelled";
        await this.storage.put(key, s);
      }
    }
  }
}

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