const express = require("express");
const { pool } = require("../db");
const {
  GAMEMODES,
  USERNAME_REGEX,
  REGIONS,
  avatarUrl,
  isValidTier,
  isValidGamemode,
  isValidRegion,
  normalizeGamemode,
  normalizeRegion,
  normalizeTier,
  tierToPoints,
} = require("../config");
const { ValidationError, validateRequest } = require("../middleware/validation");
const { requireWriteApiKey } = require("../middleware/security");
const { sendError } = require("../utils/http");

const router = express.Router();
const PER_PAGE = 20;

const TIER_POINTS_SQL = `
CASE pr.tier
  WHEN 'HT1' THEN 60
  WHEN 'LT1' THEN 45
  WHEN 'HT2' THEN 30
  WHEN 'LT2' THEN 20
  WHEN 'HT3' THEN 10
  WHEN 'LT3' THEN 6
  WHEN 'HT4' THEN 4
  WHEN 'LT4' THEN 3
  WHEN 'HT5' THEN 2
  WHEN 'LT5' THEN 1
  ELSE 0
END`;

const schemas = {
  playersQuery: {
    query: {
      fields: {
        page: { type: "int", min: 1, max: 50000 },
      },
      allowUnknown: false,
    },
  },
  usernameParam: {
    params: {
      fields: {
        username: {
          type: "string",
          required: true,
          minLength: 3,
          maxLength: 16,
          pattern: USERNAME_REGEX,
          trim: true,
        },
      },
      allowUnknown: false,
    },
  },
  createPlayerBody: {
    body: {
      fields: {
        username: {
          type: "string",
          required: true,
          minLength: 3,
          maxLength: 16,
          pattern: USERNAME_REGEX,
          trim: true,
        },
        region: {
          type: "string",
          required: false,
          minLength: 2,
          maxLength: 2,
          toUpperCase: true,
        },
      },
      allowUnknown: false,
    },
  },
  updateRegionBody: {
    body: {
      fields: {
        region: {
          type: "string",
          required: true,
          minLength: 2,
          maxLength: 2,
          toUpperCase: true,
        },
      },
      allowUnknown: false,
    },
  },
  updateRankBody: {
    body: {
      fields: {
        gamemode: {
          type: "string",
          required: true,
          minLength: 3,
          maxLength: 32,
          toLowerCase: true,
        },
        tier: {
          type: "string",
          required: true,
          minLength: 3,
          maxLength: 3,
          toUpperCase: true,
        },
        region: {
          type: "string",
          required: false,
          minLength: 2,
          maxLength: 2,
          toUpperCase: true,
        },
      },
      allowUnknown: false,
    },
  },
  removeRankParams: {
    params: {
      fields: {
        username: {
          type: "string",
          required: true,
          minLength: 3,
          maxLength: 16,
          pattern: USERNAME_REGEX,
          trim: true,
        },
        gamemode: {
          type: "string",
          required: true,
          minLength: 3,
          maxLength: 32,
          toLowerCase: true,
        },
      },
      allowUnknown: false,
    },
  },
  searchQuery: {
    query: {
      fields: {
        q: {
          type: "string",
          required: false,
          minLength: 1,
          maxLength: 16,
          pattern: /^[A-Za-z0-9_]+$/,
          trim: true,
        },
      },
      allowUnknown: false,
    },
  },
};

function validateOrReply(req, res, spec) {
  try {
    return validateRequest(req, spec);
  } catch (error) {
    if (error instanceof ValidationError) {
      sendError(res, 400, "VALIDATION_ERROR", error.message, error.details);
      return null;
    }

    sendError(res, 400, "VALIDATION_ERROR", "Invalid request payload.");
    return null;
  }
}

function emptyTierMap() {
  return Object.fromEntries(GAMEMODES.map((mode) => [mode, null]));
}

function hydratePlayersFromRows(playerRows, rankRows, startPosition = 0) {
  const byId = new Map();

  for (let i = 0; i < playerRows.length; i += 1) {
    const row = playerRows[i];
    byId.set(row.id, {
      id: row.id,
      username: row.username,
      region: row.region,
      avatarUrl: avatarUrl(row.username),
      createdAt: row.created_at,
      tiers: emptyTierMap(),
      totalPoints: Number(row.total_points || 0),
      position: startPosition + i + 1,
    });
  }

  for (const row of rankRows) {
    const target = byId.get(row.player_id);
    if (!target) {
      continue;
    }
    target.tiers[row.gamemode] = row.tier;
  }

  return Array.from(byId.values());
}

async function fetchRanksForPlayerIds(playerIds) {
  if (!playerIds.length) {
    return [];
  }

  const [rankRows] = await pool.query(
    "SELECT player_id, gamemode, tier FROM player_ranks WHERE player_id IN (?)",
    [playerIds]
  );

  return rankRows;
}

router.get("/players", async (req, res) => {
  const validated = validateOrReply(req, res, schemas.playersQuery);
  if (!validated) {
    return;
  }

  try {
    const page = validated.query.page || 1;

    const [orderedPlayers] = await pool.query(
      `SELECT
         p.id,
         p.username,
         p.region,
         p.created_at,
         COALESCE(SUM(${TIER_POINTS_SQL}), 0) AS total_points
       FROM players p
       LEFT JOIN player_ranks pr ON pr.player_id = p.id
       GROUP BY p.id, p.username, p.region, p.created_at
       ORDER BY total_points DESC, p.username ASC`
    );

    const total = orderedPlayers.length;
    const totalPages = Math.max(Math.ceil(total / PER_PAGE), 1);
    const start = (page - 1) * PER_PAGE;
    const pageRows = orderedPlayers.slice(start, start + PER_PAGE);
    const rankRows = await fetchRanksForPlayerIds(pageRows.map((row) => row.id));
    const data = hydratePlayersFromRows(pageRows, rankRows, start);

    return res.json({
      data,
      pagination: {
        page,
        perPage: PER_PAGE,
        total,
        totalPages,
      },
    });
  } catch (error) {
    console.error("GET /api/players failed:", error?.message || error);
    return sendError(res, 500, "PLAYERS_FETCH_FAILED", "Failed to fetch players");
  }
});

router.get("/players/all", async (req, res) => {
  try {
    const [orderedPlayers] = await pool.query(
      `SELECT
         p.id,
         p.username,
         p.region,
         p.created_at,
         COALESCE(SUM(${TIER_POINTS_SQL}), 0) AS total_points
       FROM players p
       LEFT JOIN player_ranks pr ON pr.player_id = p.id
       GROUP BY p.id, p.username, p.region, p.created_at
       ORDER BY total_points DESC, p.username ASC`
    );

    const rankRows = await fetchRanksForPlayerIds(orderedPlayers.map((row) => row.id));
    const data = hydratePlayersFromRows(orderedPlayers, rankRows, 0);

    return res.json({ data });
  } catch (error) {
    console.error("GET /api/players/all failed:", error?.message || error);
    return sendError(res, 500, "PLAYERS_FETCH_FAILED", "Failed to fetch all players");
  }
});

router.get("/players/:username", async (req, res) => {
  const validated = validateOrReply(req, res, schemas.usernameParam);
  if (!validated) {
    return;
  }

  try {
    const username = validated.params.username;

    const [rows] = await pool.query(
      `SELECT
         p.id,
         p.username,
         p.region,
         p.created_at,
         COALESCE(SUM(${TIER_POINTS_SQL}), 0) AS total_points
       FROM players p
       LEFT JOIN player_ranks pr ON pr.player_id = p.id
       WHERE LOWER(p.username) = LOWER(?)
       GROUP BY p.id, p.username, p.region, p.created_at
       LIMIT 1`,
      [username]
    );

    if (rows.length === 0) {
      return sendError(res, 404, "PLAYER_NOT_FOUND", "Player not found");
    }

    const player = rows[0];
    const [rankRows] = await pool.query(
      "SELECT player_id, gamemode, tier FROM player_ranks WHERE player_id = ?",
      [player.id]
    );

    const [positionRows] = await pool.query(
      `SELECT 1 + COUNT(*) AS position
       FROM (
         SELECT p.id, p.username, COALESCE(SUM(${TIER_POINTS_SQL}), 0) AS total_points
         FROM players p
         LEFT JOIN player_ranks pr ON pr.player_id = p.id
         GROUP BY p.id, p.username
       ) ranked
       WHERE ranked.total_points > ?
          OR (ranked.total_points = ? AND ranked.username < ?)`,
      [player.total_points, player.total_points, player.username]
    );

    const tiers = emptyTierMap();
    for (const row of rankRows) {
      tiers[row.gamemode] = row.tier;
    }

    return res.json({
      id: player.id,
      username: player.username,
      region: player.region,
      avatarUrl: avatarUrl(player.username),
      createdAt: player.created_at,
      tiers,
      totalPoints: Number(player.total_points || 0),
      position: Number(positionRows[0]?.position || 1),
    });
  } catch (error) {
    console.error("GET /api/players/:username failed:", error?.message || error);
    return sendError(res, 500, "PLAYER_FETCH_FAILED", "Failed to fetch player profile");
  }
});

router.post("/player", requireWriteApiKey, async (req, res) => {
  const validated = validateOrReply(req, res, schemas.createPlayerBody);
  if (!validated) {
    return;
  }

  try {
    const username = validated.body.username;
    const region = normalizeRegion(validated.body.region || "NA");

    if (!isValidRegion(region)) {
      return sendError(res, 400, "VALIDATION_ERROR", `Invalid region. Use one of: ${REGIONS.join(", ")}`);
    }

    const [insertResult] = await pool.query(
      "INSERT INTO players (username, region) VALUES (?, ?)",
      [username, region]
    );

    return res.status(201).json({
      message: "Player created",
      id: insertResult.insertId,
      username,
      region,
    });
  } catch (error) {
    if (error && error.code === "ER_DUP_ENTRY") {
      return sendError(res, 409, "USERNAME_EXISTS", "Username already exists");
    }

    console.error("POST /api/player failed:", error?.message || error);
    return sendError(res, 500, "PLAYER_CREATE_FAILED", "Failed to create player");
  }
});

router.put("/player/:username/region", requireWriteApiKey, async (req, res) => {
  const paramValidated = validateOrReply(req, res, schemas.usernameParam);
  if (!paramValidated) {
    return;
  }

  const bodyValidated = validateOrReply(req, res, schemas.updateRegionBody);
  if (!bodyValidated) {
    return;
  }

  const username = paramValidated.params.username;
  const region = normalizeRegion(bodyValidated.body.region);

  if (!isValidRegion(region)) {
    return sendError(res, 400, "VALIDATION_ERROR", `Invalid region. Use one of: ${REGIONS.join(", ")}`);
  }

  try {
    const [result] = await pool.query(
      "UPDATE players SET region = ? WHERE LOWER(username) = LOWER(?)",
      [region, username]
    );

    if (result.affectedRows === 0) {
      return sendError(res, 404, "PLAYER_NOT_FOUND", "Player not found");
    }

    return res.json({ message: "Region updated", username, region });
  } catch (error) {
    console.error("PUT /api/player/:username/region failed:", error?.message || error);
    return sendError(res, 500, "REGION_UPDATE_FAILED", "Failed to update region");
  }
});

router.delete("/player/:username", requireWriteApiKey, async (req, res) => {
  const validated = validateOrReply(req, res, schemas.usernameParam);
  if (!validated) {
    return;
  }

  try {
    const username = validated.params.username;
    const [result] = await pool.query("DELETE FROM players WHERE LOWER(username) = LOWER(?)", [username]);

    if (result.affectedRows === 0) {
      return sendError(res, 404, "PLAYER_NOT_FOUND", "Player not found");
    }

    return res.json({ message: "Player removed", username });
  } catch (error) {
    console.error("DELETE /api/player/:username failed:", error?.message || error);
    return sendError(res, 500, "PLAYER_DELETE_FAILED", "Failed to remove player");
  }
});

router.put("/player/:username/rank", requireWriteApiKey, async (req, res) => {
  const paramValidated = validateOrReply(req, res, schemas.usernameParam);
  if (!paramValidated) {
    return;
  }

  const bodyValidated = validateOrReply(req, res, schemas.updateRankBody);
  if (!bodyValidated) {
    return;
  }

  const username = paramValidated.params.username;
  const gamemode = normalizeGamemode(bodyValidated.body.gamemode);
  const tier = normalizeTier(bodyValidated.body.tier);
  const requestedRegion = bodyValidated.body.region;
  const region = requestedRegion == null ? null : normalizeRegion(requestedRegion);

  if (!isValidGamemode(gamemode)) {
    return sendError(res, 400, "VALIDATION_ERROR", "Invalid gamemode");
  }

  if (!isValidTier(tier)) {
    return sendError(res, 400, "VALIDATION_ERROR", "Invalid tier");
  }

  if (region !== null && !isValidRegion(region)) {
    return sendError(res, 400, "VALIDATION_ERROR", `Invalid region. Use one of: ${REGIONS.join(", ")}`);
  }

  try {
    const [playerRows] = await pool.query(
      "SELECT id, username, region FROM players WHERE LOWER(username) = LOWER(?) LIMIT 1",
      [username]
    );

    if (playerRows.length === 0) {
      return sendError(res, 404, "PLAYER_NOT_FOUND", "Player not found");
    }

    const player = playerRows[0];

    if (region !== null && player.region !== region) {
      await pool.query("UPDATE players SET region = ? WHERE id = ?", [region, player.id]);
      player.region = region;
    }

    await pool.query(
      `INSERT INTO player_ranks (player_id, gamemode, tier)
       VALUES (?, ?, ?)
       ON DUPLICATE KEY UPDATE tier = VALUES(tier), updated_at = CURRENT_TIMESTAMP`,
      [player.id, gamemode, tier]
    );

    return res.json({
      message: "Rank updated",
      username: player.username,
      region: player.region,
      gamemode,
      tier,
    });
  } catch (error) {
    console.error("PUT /api/player/:username/rank failed:", error?.message || error);
    return sendError(res, 500, "RANK_UPDATE_FAILED", "Failed to update rank");
  }
});

router.delete("/player/:username/rank/:gamemode", requireWriteApiKey, async (req, res) => {
  const validated = validateOrReply(req, res, schemas.removeRankParams);
  if (!validated) {
    return;
  }

  const username = validated.params.username;
  const gamemode = normalizeGamemode(validated.params.gamemode);

  if (!isValidGamemode(gamemode)) {
    return sendError(res, 400, "VALIDATION_ERROR", "Invalid gamemode");
  }

  try {
    const [playerRows] = await pool.query(
      "SELECT id, username FROM players WHERE LOWER(username) = LOWER(?) LIMIT 1",
      [username]
    );

    if (playerRows.length === 0) {
      return sendError(res, 404, "PLAYER_NOT_FOUND", "Player not found");
    }

    const [result] = await pool.query(
      "DELETE FROM player_ranks WHERE player_id = ? AND gamemode = ?",
      [playerRows[0].id, gamemode]
    );

    if (result.affectedRows === 0) {
      return sendError(res, 404, "RANK_NOT_FOUND", "No rank found for this gamemode");
    }

    return res.json({ message: "Rank removed", username: playerRows[0].username, gamemode });
  } catch (error) {
    console.error("DELETE /api/player/:username/rank/:gamemode failed:", error?.message || error);
    return sendError(res, 500, "RANK_DELETE_FAILED", "Failed to remove rank");
  }
});

router.get("/search", async (req, res) => {
  const validated = validateOrReply(req, res, schemas.searchQuery);
  if (!validated) {
    return;
  }

  try {
    const q = validated.query.q || "";

    if (!q) {
      return res.json({ data: [] });
    }

    const [matches] = await pool.query(
      `SELECT
         p.id,
         p.username,
         p.region,
         p.created_at,
         COALESCE(SUM(${TIER_POINTS_SQL}), 0) AS total_points
       FROM players p
       LEFT JOIN player_ranks pr ON pr.player_id = p.id
       WHERE p.username LIKE ?
       GROUP BY p.id, p.username, p.region, p.created_at
       ORDER BY total_points DESC, p.username ASC
       LIMIT 10`,
      [`%${q}%`]
    );

    if (matches.length === 0) {
      return res.json({ data: [] });
    }

    const rankRows = await fetchRanksForPlayerIds(matches.map((m) => m.id));
    const hydrated = hydratePlayersFromRows(matches, rankRows, 0).map((row) => {
      const next = { ...row };
      delete next.position;
      return next;
    });

    return res.json({ data: hydrated });
  } catch (error) {
    console.error("GET /api/search failed:", error?.message || error);
    return sendError(res, 500, "SEARCH_FAILED", "Failed to search players");
  }
});

module.exports = router;