import { Server } from "socket.io";
import { parse as parseCookie } from "cookie";
import type { RowDataPacket } from "mysql2";
import db from "./db.js";
import { rollDice } from "./dice.js";
import { verifyToken } from "./auth/jwt.js";
import { registerInitiativeHandlers } from "./routes/initiative.js";

interface EffectState {
  active: boolean;
  intensity: number;
}

interface AtmosphereUpdateData {
  campaignId: number;
  fog: EffectState;
  fire: EffectState;
  rain: EffectState;
  embers: EffectState;
}

export function setupSocket(io: Server) {
  // Verify JWT from cookie on every connection
  io.use((socket, next) => {
    try {
      const cookies = parseCookie(socket.handshake.headers.cookie ?? "");
      const token = cookies["darkwatch_token"];
      if (!token) throw new Error("No token");
      socket.data.user = verifyToken(token);
      next();
    } catch {
      next(new Error("Unauthorized"));
    }
  });

  io.on("connection", (socket) => {
    socket.on("join-campaign", (campaignId: string) => {
      socket.join(`campaign:${campaignId}`);
    });

    socket.on("leave-campaign", (campaignId: string) => {
      socket.leave(`campaign:${campaignId}`);
    });

    socket.on(
      "roll:request",
      async (data: {
        campaignId: number;
        characterId?: number;
        characterName?: string;
        characterColor?: string;
        type: string;
        dice: string;
        label: string;
        modifier?: number;
        advantage?: boolean;
        disadvantage?: boolean;
      }) => {
        // Verify user is a member of this campaign
        const userId = socket.data.user?.userId;
        const [memberRows] = await db.execute<RowDataPacket[]>(
          "SELECT role FROM campaign_members WHERE campaign_id = ? AND user_id = ?",
          [data.campaignId, userId]
        );
        if (memberRows.length === 0) {
          socket.emit("roll:error", { error: "Not a campaign member" });
          return;
        }
        const isDM = memberRows[0].role === "dm";

        // If rolling for a specific character, verify ownership (DMs can roll any)
        if (data.characterId && !isDM) {
          const [charRows] = await db.execute<RowDataPacket[]>(
            "SELECT user_id FROM characters WHERE id = ?",
            [data.characterId]
          );
          if (charRows.length === 0 || charRows[0].user_id !== userId) {
            socket.emit("roll:error", { error: "Cannot roll for another player's character" });
            return;
          }
        }

        const result = rollDice(data.dice, {
          advantage: data.advantage,
          disadvantage: data.disadvantage,
        });

        if (result.error) {
          socket.emit("roll:error", { error: result.error });
          return;
        }

        const isD20Roll = data.dice.match(/d20/i);
        let nat20 = false;
        if (isD20Roll && result.rolls.length > 0) {
          if (data.advantage) {
            nat20 = Math.max(...result.rolls) === 20;
          } else if (data.disadvantage) {
            nat20 = Math.min(...result.rolls) === 20;
          } else {
            nat20 = result.rolls[0] === 20;
          }
        }

        const [insertResult] = await db.execute<import("mysql2").ResultSetHeader>(
          `INSERT INTO roll_log
             (campaign_id, character_id, character_name, character_color, type, label,
              dice_expression, rolls, modifier, total, advantage, disadvantage, nat20)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
          [
            data.campaignId,
            data.characterId ?? null,
            data.characterName ?? "Roll",
            data.characterColor ?? "",
            data.type ?? "custom",
            data.label,
            data.dice,
            JSON.stringify(result.rolls),
            result.modifier,
            result.total,
            data.advantage ? 1 : 0,
            data.disadvantage ? 1 : 0,
            nat20 ? 1 : 0,
          ]
        );

        const [savedRows] = await db.execute<RowDataPacket[]>(
          "SELECT * FROM roll_log WHERE id = ?",
          [insertResult.insertId]
        );

        const broadcast = {
          ...savedRows[0],
          rolls: result.rolls,
          advantage: data.advantage ?? false,
          disadvantage: data.disadvantage ?? false,
          nat20,
        };

        io.to(`campaign:${data.campaignId}`).emit("roll:result", broadcast);
      }
    );

    socket.on("atmosphere:update", async (data: AtmosphereUpdateData) => {
      // Only DMs can broadcast atmosphere changes
      const userId = socket.data.user?.userId;
      const [rows] = await db.execute<RowDataPacket[]>(
        "SELECT role FROM campaign_members WHERE campaign_id = ? AND user_id = ?",
        [data.campaignId, userId]
      );
      if (rows.length === 0 || rows[0].role !== "dm") return;

      const { campaignId, ...atmosphere } = data;
      io.to(`campaign:${campaignId}`).emit("atmosphere:update", atmosphere);
    });

    socket.on("disconnect", () => {
      // Rooms are cleaned up automatically by Socket.IO
    });

    registerInitiativeHandlers(io, socket);
  });
}

export function broadcastToCampaign(
  io: Server,
  campaignId: number,
  event: string,
  data: unknown,
) {
  io.to(`campaign:${campaignId}`).emit(event, data);
}
