import type { SessionSnapshot } from "../shared/types";
import type { BunRequest } from "bun";
import {
  applySnapshot,
  claimTakeover,
  joinSession,
  requestTakeover,
  requestTakeoverByToken,
  snapshotSession,
} from "./domain";
import {
  createSession,
  createTestPreview,
  createTestSession,
  getSession,
  getSessionByCode,
  isTestSessionCode,
} from "./store";
import { broadcastSessionState, startTestSimulation } from "./websocket";
import { registerPushToken } from "./notifications";

function jsonResponse(data: unknown, status = 200): Response {
  return Response.json(data, { status });
}

async function readJson<T>(req: Request): Promise<T> {
  return (await req.json()) as T;
}

function clampLine(value: string, maxLength: number): string {
  return value.trim().replace(/\s+/g, " ").slice(0, maxLength);
}

function clampMessage(value: string, maxLength: number): string {
  return value.trim().slice(0, maxLength);
}

function isValidEmail(value: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

function previewSession(session: SessionSnapshot) {
  return {
    sessionId: session.id,
    code: session.code,
    status: session.status,
    players: session.players.map((player) => ({
      id: player.id,
      name: player.name,
      role: player.role,
      isDummy: player.isDummy,
      connected: player.connected,
    })),
  };
}

function readBankerId(req: Request): string | null {
  const url = new URL(req.url);
  return url.searchParams.get("bankerId");
}

function isSnapshotCandidate(value: unknown): value is SessionSnapshot {
  if (!value || typeof value !== "object") return false;
  const snapshot = value as SessionSnapshot;
  return (
    typeof snapshot.id === "string" &&
    typeof snapshot.code === "string" &&
    typeof snapshot.status === "string" &&
    typeof snapshot.createdAt === "number" &&
    typeof snapshot.bankerId === "string" &&
    typeof snapshot.blackoutActive === "boolean" &&
    Array.isArray(snapshot.players) &&
    Array.isArray(snapshot.transactions) &&
    Array.isArray(snapshot.chats) &&
    Array.isArray(snapshot.groups) &&
    Array.isArray(snapshot.takeoverRequests)
  );
}

export const apiRoutes = {
  "/api/health": {
    GET() {
      return jsonResponse({ ok: true });
    },
  },
  "/api/support": {
    async POST(req: BunRequest) {
      let body: { name?: string; email?: string; message?: string };
      try {
        body = await readJson<{ name?: string; email?: string; message?: string }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }

      const name = clampLine(body.name ?? "", 80);
      const email = clampLine(body.email ?? "", 120);
      const message = clampMessage(body.message ?? "", 2000);

      if (!name || !email || !message) {
        return jsonResponse({ message: "Name, email, and message are required" }, 400);
      }
      if (!isValidEmail(email)) {
        return jsonResponse({ message: "Invalid email address" }, 400);
      }

      const webhookUrl = process.env.DISCORD_SUPPORT_WEBHOOK_URL;
      if (!webhookUrl) {
        return jsonResponse({ message: "Support webhook not configured" }, 500);
      }

      const userAgent = (req.headers.get("user-agent") ?? "unknown").slice(0, 180);
      const payload = {
        username: "Negopoly Support",
        embeds: [
          {
            title: "New support request",
            color: 0xe39b43,
            fields: [
              { name: "Name", value: name, inline: true },
              { name: "Email", value: email, inline: true },
            ],
            description: message,
            footer: { text: `User agent: ${userAgent}` },
            timestamp: new Date().toISOString(),
          },
        ],
      };

      const response = await fetch(webhookUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });

      if (!response.ok) {
        const errorText = await response.text().catch(() => "");
        console.error("Support webhook failed:", response.status, errorText);
        return jsonResponse({ message: "Failed to forward support request" }, 502);
      }

      return jsonResponse({ ok: true });
    },
  },
  "/api/session": {
    async POST(req: BunRequest) {
      let body: { bankerName?: string };
      try {
        body = await readJson<{ bankerName?: string }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      const bankerName = body.bankerName?.trim() || "Banker";
      const { session, banker } = createSession(bankerName);
      return jsonResponse({
        sessionId: session.id,
        sessionCode: session.code,
        playerId: banker.id,
        role: banker.role,
        status: session.status,
      });
    },
  },
  "/api/session/:code/join": {
    async POST(req: BunRequest) {
      const code = req.params.code;
      let body: { name?: string; playerId?: string };
      try {
        body = await readJson<{ name?: string; playerId?: string }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      if (isTestSessionCode(code)) {
        const session = createTestSession();
        const player = joinSession(session, body.name ?? "Player", body.playerId);
        startTestSimulation(session.id);
        return jsonResponse({
          sessionId: session.id,
          sessionCode: session.code,
          playerId: player.id,
          role: player.role,
          status: session.status,
        });
      }
      const session = getSessionByCode(code) ?? getSession(code);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      if (session.status === "ended") {
        return jsonResponse({ message: "Session has ended" }, 400);
      }
      const player = joinSession(session, body.name ?? "Player", body.playerId);
      return jsonResponse({
        sessionId: session.id,
        sessionCode: session.code,
        playerId: player.id,
        role: player.role,
        status: session.status,
      });
    },
  },
  "/api/session/:id": {
    GET(req: BunRequest) {
      const session = getSession(req.params.id);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      const snapshot: SessionSnapshot = snapshotSession(session);
      return jsonResponse(snapshot);
    },
  },
  "/api/session/:id/state": {
    GET(req: BunRequest) {
      const session = getSession(req.params.id) ?? getSessionByCode(req.params.id);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      const bankerId = readBankerId(req);
      if (!bankerId || bankerId !== session.bankerId) {
        return jsonResponse({ message: "Not authorized" }, 403);
      }
      const snapshot: SessionSnapshot = snapshotSession(session);
      return jsonResponse(snapshot);
    },
    async POST(req: BunRequest) {
      const session = getSession(req.params.id) ?? getSessionByCode(req.params.id);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      let body: Record<string, unknown>;
      try {
        body = await readJson<Record<string, unknown>>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      const bankerId = (typeof body.bankerId === "string" ? body.bankerId : null) ?? readBankerId(req);
      if (!bankerId || bankerId !== session.bankerId) {
        return jsonResponse({ message: "Not authorized" }, 403);
      }

      const candidate = (body.state ?? body) as unknown;
      if (!isSnapshotCandidate(candidate)) {
        return jsonResponse({ message: "Invalid session snapshot" }, 400);
      }

      applySnapshot(session, candidate);
      broadcastSessionState(session.id);
      return jsonResponse({ ok: true });
    },
  },
  "/api/session/:code/info": {
    GET(req: BunRequest) {
      const code = req.params.code;
      if (isTestSessionCode(code)) {
        const snapshot = createTestPreview();
        return jsonResponse(previewSession(snapshot));
      }
      const session = getSessionByCode(code) ?? getSession(code);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      const snapshot: SessionSnapshot = snapshotSession(session);
      return jsonResponse(previewSession(snapshot));
    },
  },
  "/api/session/:id/takeover": {
    async POST(req: BunRequest) {
      const session = getSession(req.params.id) ?? getSessionByCode(req.params.id);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      let body: { playerId?: string; dummyId?: string };
      try {
        body = await readJson<{ playerId?: string; dummyId?: string }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      const playerId = body.playerId?.trim();
      const dummyId = body.dummyId?.trim();
      if (!playerId || !dummyId) {
        return jsonResponse({ message: "Missing playerId or dummyId" }, 400);
      }
      try {
        requestTakeover(session, playerId, dummyId);
      } catch (error) {
        const message = error instanceof Error ? error.message : "Unable to request takeover";
        return jsonResponse({ message }, 400);
      }
      broadcastSessionState(session.id);
      return jsonResponse({ ok: true });
    },
  },
  "/api/session/:id/takeover-request": {
    async POST(req: BunRequest) {
      const session = getSession(req.params.id) ?? getSessionByCode(req.params.id);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      let body: { dummyId?: string; name?: string };
      try {
        body = await readJson<{ dummyId?: string; name?: string }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      const dummyId = body.dummyId?.trim();
      if (!dummyId) {
        return jsonResponse({ message: "Missing dummyId" }, 400);
      }
      try {
        const token = requestTakeoverByToken(session, dummyId, body.name);
        broadcastSessionState(session.id);
        return jsonResponse({ token });
      } catch (error) {
        const message = error instanceof Error ? error.message : "Unable to request takeover";
        return jsonResponse({ message }, 400);
      }
    },
  },
  "/api/session/:id/takeover-claim": {
    async POST(req: BunRequest) {
      const session = getSession(req.params.id) ?? getSessionByCode(req.params.id);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      let body: { token?: string };
      try {
        body = await readJson<{ token?: string }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      const token = body.token?.trim();
      if (!token) {
        return jsonResponse({ message: "Missing token" }, 400);
      }
      try {
        const playerId = claimTakeover(session, token);
        broadcastSessionState(session.id);
        return jsonResponse({
          sessionId: session.id,
          sessionCode: session.code,
          playerId,
          role: "player",
          status: session.status,
        });
      } catch (error) {
        const message = error instanceof Error ? error.message : "Unable to claim takeover";
        if (message === "Takeover not approved") {
          return jsonResponse({ message }, 409);
        }
        return jsonResponse({ message }, 400);
      }
    },
  },
  "/api/push/register": {
    async POST(req: BunRequest) {
      let body: { sessionId?: string; playerId?: string; token?: string; platform?: string };
      try {
        body = await readJson<{
          sessionId?: string;
          playerId?: string;
          token?: string;
          platform?: string;
        }>(req);
      } catch {
        return jsonResponse({ message: "Invalid request body" }, 400);
      }
      const sessionId = body.sessionId?.trim();
      const playerId = body.playerId?.trim();
      const token = body.token?.trim();
      const platform = body.platform?.trim();
      if (!sessionId || !playerId || !token || !platform) {
        return jsonResponse({ message: "Missing sessionId, playerId, token, or platform" }, 400);
      }
      if (platform !== "ios" && platform !== "android") {
        return jsonResponse({ message: "Invalid platform" }, 400);
      }
      const session = getSession(sessionId);
      if (!session) {
        return jsonResponse({ message: "Session not found" }, 404);
      }
      const player = session.players.get(playerId);
      if (!player) {
        return jsonResponse({ message: "Player not found" }, 404);
      }
      registerPushToken(sessionId, playerId, token, platform);
      return jsonResponse({ ok: true });
    },
  },
};
