import { pool } from "./pools.js";
import { loadConfigFile, getConfig } from "../configLoader.js";
import { computeWorldSeedState } from "../worldSeed.js";
import { nextNoonUtc, resetAllUserActions } from "./usersDb.js";

let lastSeedSlot = null;

// ── Schema ────────────────────────────────────────────────────────────────────

export async function initGameSchema() {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS grid_cells (
      id SERIAL PRIMARY KEY,
      world_seed TEXT NOT NULL,
      x SMALLINT NOT NULL CHECK (x >= 0 AND x < 100),
      y SMALLINT NOT NULL CHECK (y >= 0 AND y < 100),
      exploitable BOOLEAN NOT NULL,
      has_planet BOOLEAN NOT NULL,
      planet_json JSONB,
      created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
      UNIQUE (world_seed, x, y)
    );
    CREATE INDEX IF NOT EXISTS idx_grid_cells_seed ON grid_cells (world_seed);
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS team_cooldowns (
      world_seed TEXT NOT NULL,
      team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
      last_reveal TIMESTAMPTZ,
      PRIMARY KEY (world_seed, team)
    );
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS user_cooldowns (
      world_seed TEXT NOT NULL,
      user_id INTEGER NOT NULL,
      team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
      last_reveal TIMESTAMPTZ,
      PRIMARY KEY (world_seed, user_id)
    );
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS team_econ_scores (
      world_seed TEXT NOT NULL,
      team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
      score DOUBLE PRECISION NOT NULL DEFAULT 0,
      PRIMARY KEY (world_seed, team)
    );
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS team_element_bonus (
      world_seed TEXT NOT NULL,
      team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
      bonus DOUBLE PRECISION NOT NULL DEFAULT 0,
      PRIMARY KEY (world_seed, team)
    );
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS team_military_deductions (
      world_seed TEXT NOT NULL,
      team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
      deducted DOUBLE PRECISION NOT NULL DEFAULT 0,
      PRIMARY KEY (world_seed, team)
    );
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS cell_attack_log (
      id SERIAL PRIMARY KEY,
      world_seed TEXT NOT NULL,
      x SMALLINT NOT NULL,
      y SMALLINT NOT NULL,
      attacking_team TEXT NOT NULL CHECK (attacking_team IN ('blue', 'red')),
      attacked_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    );
    CREATE INDEX IF NOT EXISTS idx_cell_attack_log_seed_xy
      ON cell_attack_log (world_seed, x, y);
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS db_metadata (
      id SERIAL PRIMARY KEY,
      created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    );
    INSERT INTO db_metadata (created_at)
    SELECT NOW() WHERE NOT EXISTS (SELECT 1 FROM db_metadata);
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS victory_points (
      id SERIAL PRIMARY KEY,
      awarded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
      world_seed TEXT NOT NULL,
      team TEXT NOT NULL CHECK (team IN ('blue', 'red'))
    );
    CREATE INDEX IF NOT EXISTS idx_vp_team ON victory_points (team);
  `);
  await pool.query(`
    ALTER TABLE grid_cells ADD COLUMN IF NOT EXISTS discovered_by TEXT;
    UPDATE grid_cells SET discovered_by = 'blue' WHERE discovered_by IS NULL;
    ALTER TABLE grid_cells ALTER COLUMN discovered_by SET DEFAULT 'blue';
    DO $$
    BEGIN
      IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'grid_cells_discovered_by_check'
      ) THEN
        ALTER TABLE grid_cells ADD CONSTRAINT grid_cells_discovered_by_check
          CHECK (discovered_by IN ('blue', 'red'));
      END IF;
    END $$;
    ALTER TABLE grid_cells ALTER COLUMN discovered_by SET NOT NULL;
  `);
  await pool.query(`
    CREATE TABLE IF NOT EXISTS team_action_quota (
      team TEXT PRIMARY KEY CHECK (team IN ('blue', 'red')),
      actions_remaining INTEGER NOT NULL DEFAULT 0,
      quota_reset_at TIMESTAMPTZ NOT NULL DEFAULT '1970-01-01 00:00:00+00'
    );
  `);
  // ── Per-team cell visibility (who has revealed what) ──────────────────────
  await pool.query(`
    CREATE TABLE IF NOT EXISTS team_cell_visibility (
      world_seed TEXT NOT NULL,
      team       TEXT NOT NULL CHECK (team IN ('blue', 'red')),
      x          SMALLINT NOT NULL,
      y          SMALLINT NOT NULL,
      PRIMARY KEY (world_seed, team, x, y)
    );
    CREATE INDEX IF NOT EXISTS idx_tcv_seed_team ON team_cell_visibility (world_seed, team);
  `);
  // ── Make discovered_by nullable (NULL = neutral, uncaptured) ─────────────
  await pool.query(`
    ALTER TABLE grid_cells ALTER COLUMN discovered_by DROP NOT NULL;
    ALTER TABLE grid_cells ALTER COLUMN discovered_by DROP DEFAULT;
    DO $$
    BEGIN
      IF EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'grid_cells_discovered_by_check'
      ) THEN
        ALTER TABLE grid_cells DROP CONSTRAINT grid_cells_discovered_by_check;
      END IF;
    END $$;
    ALTER TABLE grid_cells ADD CONSTRAINT grid_cells_discovered_by_check
      CHECK (discovered_by IS NULL OR discovered_by IN ('blue', 'red'));
  `);
  // ── Store the username of whoever last captured a tile ────────────────────
  await pool.query(`
    ALTER TABLE grid_cells ADD COLUMN IF NOT EXISTS captured_by TEXT;
  `);
}

// ── World-seed epoch ──────────────────────────────────────────────────────────

export async function ensureSeedEpoch() {
  loadConfigFile();
  const rot = getConfig().databaseWipeoutIntervalSeconds;
  const { seedSlot, worldSeed } = computeWorldSeedState(rot);
  if (lastSeedSlot === null) {
    lastSeedSlot = seedSlot;
    return worldSeed;
  }
  if (seedSlot !== lastSeedSlot) {
    // Award a victory point to the team with the highest econ score before wiping
    try {
      const expiredSeed = `swg-${lastSeedSlot}`;
      const econRows = await pool.query(
        `SELECT team, score FROM team_econ_scores WHERE world_seed = $1`,
        [expiredSeed]
      );
      const scores = { blue: 0, red: 0 };
      for (const row of econRows.rows) scores[row.team] = Number(row.score);
      if (scores.blue > 0 || scores.red > 0) {
        const winner = scores.blue >= scores.red ? "blue" : "red";
        await pool.query(
          `INSERT INTO victory_points (world_seed, team) VALUES ($1, $2)`,
          [expiredSeed, winner]
        );
        console.log(`[world] VP awarded to ${winner} for seed ${expiredSeed} (blue=${scores.blue.toFixed(3)}, red=${scores.red.toFixed(3)})`);
      }
    } catch (e) {
      console.error("[world] VP award error:", e);
    }
    await pool.query("TRUNCATE grid_cells RESTART IDENTITY");
    await pool.query("DELETE FROM team_cooldowns WHERE world_seed != $1", [worldSeed]);
    await pool.query("DELETE FROM user_cooldowns WHERE world_seed != $1", [worldSeed]);
    await pool.query("DELETE FROM team_econ_scores WHERE world_seed != $1", [worldSeed]);
    await pool.query("DELETE FROM team_element_bonus WHERE world_seed != $1", [worldSeed]);
    await pool.query("DELETE FROM team_military_deductions WHERE world_seed != $1", [worldSeed]);
    await pool.query("DELETE FROM cell_attack_log WHERE world_seed != $1", [worldSeed]);
    await pool.query("DELETE FROM team_cell_visibility WHERE world_seed != $1", [worldSeed]);
    // Reset both team and user quotas to server defaults (no bonuses) for the new period
    const cfg = getConfig();
    const nextNoon = nextNoonUtc().toISOString();
    await pool.query(
      `INSERT INTO team_action_quota (team, actions_remaining, quota_reset_at)
       VALUES ('blue', $1, $2), ('red', $1, $2)
       ON CONFLICT (team) DO UPDATE
         SET actions_remaining = $1,
             quota_reset_at    = $2`,
      [cfg.teamActionQuota, nextNoon]
    );
    await resetAllUserActions(cfg.dailyActionQuota, nextNoon);
    console.log(`[world] Slot ${lastSeedSlot} → ${seedSlot}; grid wiped, old cooldowns cleared.`);
    lastSeedSlot = seedSlot;
  }
  return worldSeed;
}

// ── Grid cells ────────────────────────────────────────────────────────────────

export async function getGridCells(worldSeed) {
  const { rows } = await pool.query(
    `SELECT x, y, exploitable, has_planet, planet_json, discovered_by, captured_by
     FROM grid_cells WHERE world_seed = $1`,
    [worldSeed]
  );
  return rows;
}

export async function insertCell(seed, x, y, exploitable, hasPlanet, planetJson) {
  const { rows } = await pool.query(
    `INSERT INTO grid_cells (world_seed, x, y, exploitable, has_planet, planet_json, discovered_by)
     VALUES ($1, $2, $3, $4, $5, $6, NULL)
     ON CONFLICT (world_seed, x, y) DO NOTHING
     RETURNING x, y, exploitable, has_planet, planet_json, discovered_by`,
    [seed, x, y, exploitable, hasPlanet, planetJson]
  );
  return rows[0] ?? null;
}

// ── Team cell visibility ──────────────────────────────────────────────────────

/** Returns true if team has already revealed this cell. */
export async function checkTeamVisibility(worldSeed, team, x, y) {
  const { rows } = await pool.query(
    `SELECT 1 FROM team_cell_visibility WHERE world_seed = $1 AND team = $2 AND x = $3 AND y = $4`,
    [worldSeed, team, x, y]
  );
  return rows.length > 0;
}

/** Inserts a visibility record. Returns true if newly inserted (first reveal). */
export async function insertTeamVisibility(worldSeed, team, x, y) {
  const { rowCount } = await pool.query(
    `INSERT INTO team_cell_visibility (world_seed, team, x, y)
     VALUES ($1, $2, $3, $4)
     ON CONFLICT (world_seed, team, x, y) DO NOTHING`,
    [worldSeed, team, x, y]
  );
  return rowCount > 0;
}

/** Returns all grid cells that are visible to a given team. */
export async function getTeamVisibleCells(worldSeed, team) {
  const { rows } = await pool.query(
    `SELECT gc.x, gc.y, gc.exploitable, gc.has_planet, gc.planet_json, gc.discovered_by, gc.captured_by
     FROM grid_cells gc
     JOIN team_cell_visibility tcv
       ON tcv.world_seed = gc.world_seed AND tcv.x = gc.x AND tcv.y = gc.y
     WHERE gc.world_seed = $1 AND tcv.team = $2`,
    [worldSeed, team]
  );
  return rows;
}

export async function getExistingCell(seed, x, y) {
  const { rows } = await pool.query(
    `SELECT x, y, exploitable, has_planet, planet_json, discovered_by, captured_by
     FROM grid_cells WHERE world_seed = $1 AND x = $2 AND y = $3`,
    [seed, x, y]
  );
  return rows[0] ?? null;
}

// ── Team cooldowns ────────────────────────────────────────────────────────────

export async function getTeamCooldown(worldSeed, team) {
  const { rows } = await pool.query(
    `SELECT last_reveal FROM team_cooldowns WHERE world_seed = $1 AND team = $2`,
    [worldSeed, team]
  );
  return rows[0] ?? null;
}

export async function upsertTeamCooldown(worldSeed, team) {
  await pool.query(
    `INSERT INTO team_cooldowns (world_seed, team, last_reveal)
     VALUES ($1, $2, NOW())
     ON CONFLICT (world_seed, team) DO UPDATE SET last_reveal = NOW()`,
    [worldSeed, team]
  );
}

// ── User cooldowns (per-user, replaces team-wide cooldown for reveal) ─────────

export async function getUserCooldown(worldSeed, userId) {
  const { rows } = await pool.query(
    `SELECT last_reveal FROM user_cooldowns WHERE world_seed = $1 AND user_id = $2`,
    [worldSeed, userId]
  );
  return rows[0] ?? null;
}

export async function upsertUserCooldown(worldSeed, userId, team) {
  await pool.query(
    `INSERT INTO user_cooldowns (world_seed, user_id, team, last_reveal)
     VALUES ($1, $2, $3, NOW())
     ON CONFLICT (world_seed, user_id) DO UPDATE SET last_reveal = NOW()`,
    [worldSeed, userId, team]
  );
}

// ── Economic scores ───────────────────────────────────────────────────────────

export async function getEconScores(worldSeed) {
  const { rows } = await pool.query(
    `SELECT team, score FROM team_econ_scores WHERE world_seed = $1`,
    [worldSeed]
  );
  const result = { blue: 0, red: 0 };
  for (const row of rows) result[row.team] = Number(row.score);
  return result;
}

export async function addEconScore(worldSeed, team, delta) {
  if (delta <= 0) return;
  await pool.query(
    `INSERT INTO team_econ_scores (world_seed, team, score)
     VALUES ($1, $2, $3)
     ON CONFLICT (world_seed, team) DO UPDATE
       SET score = team_econ_scores.score + EXCLUDED.score`,
    [worldSeed, team, delta]
  );
}

export async function getElementBonus(worldSeed) {
  const { rows } = await pool.query(
    `SELECT team, bonus FROM team_element_bonus WHERE world_seed = $1`,
    [worldSeed]
  );
  const result = { blue: 0, red: 0 };
  for (const row of rows) result[row.team] = Number(row.bonus);
  return result;
}

export async function setElementBonus(worldSeed, team, bonus) {
  await pool.query(
    `INSERT INTO team_element_bonus (world_seed, team, bonus)
     VALUES ($1, $2, $3)
     ON CONFLICT (world_seed, team) DO UPDATE
       SET bonus = EXCLUDED.bonus`,
    [worldSeed, team, bonus]
  );
}

// ── DB metadata ───────────────────────────────────────────────────────────────

export async function getDbCreatedAt() {
  const { rows } = await pool.query(
    `SELECT created_at FROM db_metadata ORDER BY id ASC LIMIT 1`
  );
  return rows[0]?.created_at ?? null;
}

// ── Victory points ────────────────────────────────────────────────────────────

export async function getVictoryPoints() {
  const { rows } = await pool.query(
    `SELECT team, COUNT(*) AS cnt FROM victory_points GROUP BY team`
  );
  const result = { blue: 0, red: 0 };
  for (const row of rows) result[row.team] = Number(row.cnt);
  return result;
}

// ── Scores ────────────────────────────────────────────────────────────────────

export async function getScores(worldSeed) {
  const { rows } = await pool.query(
    `SELECT discovered_by, COUNT(*) AS cnt
     FROM grid_cells WHERE world_seed = $1 AND discovered_by IS NOT NULL
     GROUP BY discovered_by`,
    [worldSeed]
  );
  return rows;
}

// ── Active player counts (players who have played in the current epoch) ───────

export async function getActivePlayerCounts(worldSeed) {
  const { rows } = await pool.query(
    `SELECT team, COUNT(DISTINCT user_id)::int AS count
     FROM user_cooldowns WHERE world_seed = $1
     GROUP BY team`,
    [worldSeed]
  );
  const result = { blue: 0, red: 0 };
  for (const row of rows) result[row.team] = row.count;
  return result;
}

// ── Military deductions ───────────────────────────────────────────────────────

export async function getMilitaryDeductions(worldSeed) {
  const { rows } = await pool.query(
    `SELECT team, deducted FROM team_military_deductions WHERE world_seed = $1`,
    [worldSeed]
  );
  const result = { blue: 0, red: 0 };
  for (const row of rows) result[row.team] = Number(row.deducted);
  return result;
}

export async function addMilitaryDeduction(worldSeed, team, amount) {
  await pool.query(
    `INSERT INTO team_military_deductions (world_seed, team, deducted)
     VALUES ($1, $2, $3)
     ON CONFLICT (world_seed, team) DO UPDATE
       SET deducted = team_military_deductions.deducted + EXCLUDED.deducted`,
    [worldSeed, team, amount]
  );
}

// ── Cell attack log ───────────────────────────────────────────────────────────

export async function recordCellAttack(worldSeed, x, y, attackingTeam) {
  await pool.query(
    `INSERT INTO cell_attack_log (world_seed, x, y, attacking_team) VALUES ($1, $2, $3, $4)`,
    [worldSeed, x, y, attackingTeam]
  );
}

export async function getCellAttackCount(worldSeed, x, y) {
  const { rows } = await pool.query(
    `SELECT COUNT(*)::int AS cnt FROM cell_attack_log WHERE world_seed = $1 AND x = $2 AND y = $3`,
    [worldSeed, x, y]
  );
  return rows[0]?.cnt ?? 0;
}

export async function setTileOwner(worldSeed, x, y, team, capturedBy = null) {
  await pool.query(
    `UPDATE grid_cells SET discovered_by = $1, captured_by = $2 WHERE world_seed = $3 AND x = $4 AND y = $5`,
    [team, capturedBy, worldSeed, x, y]
  );
}

/** Returns per-team lists of user IDs who have been active this seed epoch. */
export async function getActivePlayerIds(worldSeed) {
  const { rows } = await pool.query(
    `SELECT team, user_id FROM user_cooldowns WHERE world_seed = $1`,
    [worldSeed]
  );
  const result = { blue: [], red: [] };
  for (const row of rows) {
    if (result[row.team]) result[row.team].push(row.user_id);
  }
  return result;
}

// ── Team action quota (daily, independent of world seed) ─────────────────────

export async function getTeamActionsRow(team) {
  const { rows } = await pool.query(
    `SELECT actions_remaining, quota_reset_at FROM team_action_quota WHERE team = $1`,
    [team]
  );
  return rows[0] ?? null;
}

export async function resetTeamActions(team, actionsRemaining, quotaResetAt) {
  await pool.query(
    `INSERT INTO team_action_quota (team, actions_remaining, quota_reset_at)
     VALUES ($1, $2, $3)
     ON CONFLICT (team) DO UPDATE
       SET actions_remaining = $2,
           quota_reset_at    = $3`,
    [team, actionsRemaining, quotaResetAt]
  );
}

/**
 * Atomically decrements team actions_remaining by 1 if > 0.
 * Returns the updated row, or null if already 0.
 */
export async function decrementTeamActions(team) {
  const { rows } = await pool.query(
    `UPDATE team_action_quota
     SET actions_remaining = actions_remaining - 1
     WHERE team = $1 AND actions_remaining > 0
     RETURNING actions_remaining`,
    [team]
  );
  return rows[0] ?? null;
}

/**
 * Atomically decrements team actions_remaining by `amount` if sufficient.
 * Returns the updated row, or null if not enough actions.
 */
export async function decrementTeamActionsBy(team, amount) {
  const { rows } = await pool.query(
    `UPDATE team_action_quota
     SET actions_remaining = actions_remaining - $2
     WHERE team = $1 AND actions_remaining >= $2
     RETURNING actions_remaining`,
    [team, amount]
  );
  return rows[0] ?? null;
}
