import { Router } from "express";
import type { ParamsDictionary } from "express-serve-static-core";
import type { Server } from "socket.io";
import db from "../db.js";
import { broadcastToCampaign } from "../socket.js";

type CampaignParams = ParamsDictionary & { campaignId: string };

const router = Router({ mergeParams: true });

const DEFAULT_STATS = ["STR", "DEX", "CON", "INT", "WIS", "CHA"];

function generateCharacterColor(): string {
  const hue = Math.floor(Math.random() * 360);
  return `hsl(${hue}, 60%, 65%)`;
}

function parseJson(val: unknown): Record<string, unknown> {
  if (typeof val === "string") {
    try {
      return JSON.parse(val);
    } catch {
      return {};
    }
  }
  return (val as Record<string, unknown>) ?? {};
}

function parseGear(rows: Array<Record<string, unknown>>) {
  return rows.map((r) => ({
    ...r,
    properties: parseJson(r.properties),
    effects: parseJson(r.effects),
  }));
}

function parseTalents(rows: Array<Record<string, unknown>>) {
  return rows.map((r) => ({ ...r, effect: parseJson(r.effect) }));
}

// GET /api/campaigns/:campaignId/characters — list characters in a campaign
router.get<CampaignParams>("/", (req, res) => {
  const { campaignId } = req.params;
  const characters = db
    .prepare("SELECT * FROM characters WHERE campaign_id = ? ORDER BY name")
    .all(campaignId) as Array<Record<string, unknown>>;

  const stmtStats = db.prepare(
    "SELECT stat_name, value FROM character_stats WHERE character_id = ?",
  );
  const stmtGear = db.prepare(
    "SELECT * FROM character_gear WHERE character_id = ?",
  );
  const stmtTalents = db.prepare(
    "SELECT * FROM character_talents WHERE character_id = ?",
  );

  const enriched = characters.map((char) => ({
    ...char,
    overrides: parseJson(char.overrides),
    stats: stmtStats.all(char.id),
    gear: parseGear(stmtGear.all(char.id) as Array<Record<string, unknown>>),
    talents: parseTalents(
      stmtTalents.all(char.id) as Array<Record<string, unknown>>,
    ),
  }));

  res.json(enriched);
});

// POST /api/campaigns/:campaignId/characters — create a character
router.post<CampaignParams>("/", (req, res) => {
  const { campaignId } = req.params;
  const { name, class: charClass, ancestry, hp_max } = req.body;

  if (!name || !name.trim()) {
    res.status(400).json({ error: "Character name is required" });
    return;
  }

  const insertChar = db.prepare(`
        INSERT INTO characters (campaign_id, name, class, ancestry, hp_current, hp_max, color)
        VALUES (?, ?, ?, ?, ?, ?, ?)
    `);

  const insertStat = db.prepare(
    "INSERT INTO character_stats (character_id, stat_name, value) VALUES (?, ?, 10)",
  );

  const result = insertChar.run(
    campaignId,
    name.trim(),
    charClass || "Fighter",
    ancestry || "Human",
    hp_max || 0,
    hp_max || 0,
    generateCharacterColor(),
  );
  const characterId = result.lastInsertRowid;

  for (const stat of DEFAULT_STATS) {
    insertStat.run(characterId, stat);
  }

  const character = db
    .prepare("SELECT * FROM characters WHERE id = ?")
    .get(characterId);
  const stats = db
    .prepare(
      "SELECT stat_name, value FROM character_stats WHERE character_id = ?",
    )
    .all(characterId);

  const enriched = {
    ...(character as Record<string, unknown>),
    overrides: {},
    stats,
    gear: [],
    talents: [],
  };

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(campaignId), "character:created", enriched);

  res.status(201).json(enriched);
});

// PATCH /api/characters/:id — update character fields
router.patch("/:id", (req, res) => {
  const { id } = req.params;
  const allowedFields = [
    "name",
    "class",
    "ancestry",
    "level",
    "xp",
    "hp_current",
    "hp_max",
    "ac",
    "alignment",
    "title",
    "notes",
    "background",
    "deity",
    "languages",
    "gp",
    "sp",
    "cp",
    "gear_slots_max",
    "overrides",
    "color",
    "luck_token",
    "torch_lit_at",
  ];

  const updates: string[] = [];
  const values: unknown[] = [];

  for (const field of allowedFields) {
    if (req.body[field] !== undefined) {
      updates.push(`${field} = ?`);
      const val = req.body[field];
      // JSON-stringify object fields for SQLite TEXT storage
      values.push(
        typeof val === "object" && val !== null ? JSON.stringify(val) : val,
      );
    }
  }

  if (updates.length === 0) {
    res.status(400).json({ error: "No valid fields to update" });
    return;
  }

  values.push(id);
  db.prepare(`UPDATE characters SET ${updates.join(", ")} WHERE id = ?`).run(
    ...values,
  );

  const character = db
    .prepare("SELECT * FROM characters WHERE id = ?")
    .get(id) as Record<string, unknown>;
  if (!character) {
    res.status(404).json({ error: "Character not found" });
    return;
  }

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "character:updated", {
    id: Number(id),
    ...req.body,
  });

  res.json(character);
});

// DELETE /api/characters/:id — delete a character
router.delete("/:id", (req, res) => {
  const character = db
    .prepare("SELECT * FROM characters WHERE id = ?")
    .get(req.params.id) as Record<string, unknown> | undefined;
  if (!character) {
    res.status(404).json({ error: "Character not found" });
    return;
  }

  db.prepare("DELETE FROM characters WHERE id = ?").run(req.params.id);

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "character:deleted", {
    id: Number(req.params.id),
  });

  res.status(204).end();
});

// PATCH /api/characters/:id/stats/:statName — update a single stat
router.patch("/:id/stats/:statName", (req, res) => {
  const { id, statName } = req.params;
  const { value } = req.body;
  const upper = statName.toUpperCase();

  if (!DEFAULT_STATS.includes(upper)) {
    res.status(400).json({ error: "Invalid stat name" });
    return;
  }

  db.prepare(
    "UPDATE character_stats SET value = ? WHERE character_id = ? AND stat_name = ?",
  ).run(value, id, upper);

  const character = db
    .prepare("SELECT campaign_id FROM characters WHERE id = ?")
    .get(id) as Record<string, unknown>;
  if (!character) {
    res.status(404).json({ error: "Character not found" });
    return;
  }

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "stat:updated", {
    characterId: Number(id),
    statName: upper,
    value,
  });

  res.json({ characterId: Number(id), statName: upper, value });
});

// POST /api/characters/:id/gear — add gear
router.post("/:id/gear", (req, res) => {
  const { id } = req.params;
  const { name, type, slot_count, properties, effects, game_item_id } =
    req.body;

  if (!name || !name.trim()) {
    res.status(400).json({ error: "Gear name is required" });
    return;
  }

  const result = db
    .prepare(
      "INSERT INTO character_gear (character_id, name, type, slot_count, properties, effects, game_item_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
    )
    .run(
      id,
      name.trim(),
      type || "gear",
      slot_count ?? 1,
      JSON.stringify(properties || {}),
      JSON.stringify(effects || {}),
      game_item_id ?? null,
    );

  const gearRow = db
    .prepare("SELECT * FROM character_gear WHERE id = ?")
    .get(result.lastInsertRowid) as Record<string, unknown>;
  const gear = {
    ...gearRow,
    properties: parseJson(gearRow.properties),
    effects: parseJson(gearRow.effects),
  };

  const character = db
    .prepare("SELECT campaign_id FROM characters WHERE id = ?")
    .get(id) as Record<string, unknown>;

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "gear:added", {
    characterId: Number(id),
    gear,
  });

  res.status(201).json(gear);
});

// DELETE /api/characters/:id/gear/:gearId — remove gear
router.delete("/:id/gear/:gearId", (req, res) => {
  const { id, gearId } = req.params;
  const character = db
    .prepare("SELECT campaign_id FROM characters WHERE id = ?")
    .get(id) as Record<string, unknown>;
  if (!character) {
    res.status(404).json({ error: "Character not found" });
    return;
  }

  const result = db
    .prepare("DELETE FROM character_gear WHERE id = ? AND character_id = ?")
    .run(gearId, id);
  if (result.changes === 0) {
    res.status(404).json({ error: "Gear not found" });
    return;
  }

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "gear:removed", {
    characterId: Number(id),
    gearId: Number(gearId),
  });

  res.status(204).end();
});

// POST /api/characters/:id/talents — add talent
router.post("/:id/talents", (req, res) => {
  const { id } = req.params;
  const { name, description, effect, game_talent_id } = req.body;

  if (!name || !name.trim()) {
    res.status(400).json({ error: "Talent name is required" });
    return;
  }

  const result = db
    .prepare(
      "INSERT INTO character_talents (character_id, name, description, effect, game_talent_id) VALUES (?, ?, ?, ?, ?)",
    )
    .run(
      id,
      name.trim(),
      description || "",
      JSON.stringify(effect || {}),
      game_talent_id ?? null,
    );

  const talentRow = db
    .prepare("SELECT * FROM character_talents WHERE id = ?")
    .get(result.lastInsertRowid) as Record<string, unknown>;
  const talent = { ...talentRow, effect: parseJson(talentRow.effect) };

  const character = db
    .prepare("SELECT campaign_id FROM characters WHERE id = ?")
    .get(id) as Record<string, unknown>;

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "talent:added", {
    characterId: Number(id),
    talent,
  });

  res.status(201).json(talent);
});

// DELETE /api/characters/:id/talents/:talentId — remove talent
router.delete("/:id/talents/:talentId", (req, res) => {
  const { id, talentId } = req.params;
  const character = db
    .prepare("SELECT campaign_id FROM characters WHERE id = ?")
    .get(id) as Record<string, unknown>;
  if (!character) {
    res.status(404).json({ error: "Character not found" });
    return;
  }

  const result = db
    .prepare("DELETE FROM character_talents WHERE id = ? AND character_id = ?")
    .run(talentId, id);
  if (result.changes === 0) {
    res.status(404).json({ error: "Talent not found" });
    return;
  }

  const io: Server = req.app.get("io");
  broadcastToCampaign(io, Number(character.campaign_id), "talent:removed", {
    characterId: Number(id),
    talentId: Number(talentId),
  });

  res.status(204).end();
});

export default router;
