import pg from "pg";
import parse from "pg-connection-string";
import { createInitialBotState } from "./bot-state.js";

const { Pool } = pg;

const connectionString = process.env.DATABASE_URL || "postgres://localhost/builazoo";
const parsed = parse(connectionString);
const poolConfig = {
  host: parsed.host || "localhost",
  port: Number(parsed.port) || 5432,
  database: parsed.database || "builazoo",
  user: parsed.user,
  password: typeof parsed.password === "string" ? parsed.password : "",
};
if (process.env.PGPASSWORD !== undefined && process.env.PGPASSWORD !== null) poolConfig.password = String(process.env.PGPASSWORD);

const pool = new Pool(poolConfig);

/**
 * @returns {Promise<{ mapWidth: number, mapHeight: number, minZoos: number }>}
 */
export async function getMapParams() {
  const res = await pool.query(
    "SELECT value FROM map_config WHERE key = 'params'"
  );
  const row = res.rows[0];
  if (!row) {
    return { mapWidth: 100, mapHeight: 100, minZoos: 5 };
  }
  const v = row.value;
  return {
    mapWidth: Number(v?.mapWidth) || 100,
    mapHeight: Number(v?.mapHeight) || 100,
    minZoos: Number(v?.minZoos) || 5,
  };
}

/**
 * @param {string} publicKey
 * @returns {Promise<{ id: string, pseudo: string } | null>}
 */
export async function getAccountByPublicKey(publicKey) {
  const res = await pool.query(
    "SELECT id, pseudo FROM accounts WHERE public_key = $1",
    [publicKey]
  );
  const row = res.rows[0];
  if (!row) return null;
  return { id: row.id, pseudo: row.pseudo };
}

/**
 * @param {string} publicKey
 * @param {string} pseudo
 * @returns {Promise<{ id: string, pseudo: string }>}
 */
export async function createAccount(publicKey, pseudo) {
  const res = await pool.query(
    "INSERT INTO accounts (public_key, pseudo) VALUES ($1, $2) RETURNING id, pseudo",
    [publicKey, pseudo]
  );
  const row = res.rows[0];
  return { id: row.id, pseudo: row.pseudo };
}

/**
 * @param {string} accountId
 * @returns {Promise<void>}
 */
export async function updateLastSeen(accountId) {
  await pool.query(
    "UPDATE accounts SET last_seen_at = now() WHERE id = $1",
    [accountId]
  );
}

/**
 * @param {string} accountId
 * @returns {Promise<{ id: string, name: string, x: number, y: number, is_bot: boolean, animal_weights: object, game_state: object | null } | null>}
 */
export async function getZooByAccountId(accountId) {
  const res = await pool.query(
    "SELECT id, name, x, y, is_bot, animal_weights, game_state FROM zoos WHERE account_id = $1",
    [accountId]
  );
  const row = res.rows[0];
  if (!row) return null;
  return {
    id: row.id,
    name: row.name,
    x: Number(row.x),
    y: Number(row.y),
    is_bot: row.is_bot,
    animal_weights: row.animal_weights || {},
    game_state: row.game_state,
  };
}

/**
 * Common zoo row fields: id, name, x, y with numeric coords.
 * @param {Record<string, unknown>} row
 * @returns {{ id: string, name: string, x: number, y: number }}
 */
function mapZooRowBase(row) {
  return {
    id: row.id,
    name: row.name,
    x: Number(row.x),
    y: Number(row.y),
  };
}

/**
 * @returns {Promise<Array<{ id: string, name: string, x: number, y: number, animal_weights: object, game_state: object | null }>>}
 */
export async function getAllZoos() {
  const res = await pool.query(
    "SELECT id, name, x, y, animal_weights, game_state FROM zoos ORDER BY is_bot, name"
  );
  return res.rows.map((row) => ({
    ...mapZooRowBase(row),
    animal_weights: row.animal_weights || {},
    game_state: row.game_state ?? null,
  }));
}

/**
 * @param {{ accountId: string, name: string, x: number, y: number, gameState: object }} opts
 * @returns {Promise<{ id: string }>}
 */
export async function createZoo(opts) {
  const { accountId, name, x, y, gameState } = opts;
  const res = await pool.query(
    "INSERT INTO zoos (account_id, name, x, y, is_bot, animal_weights, game_state) VALUES ($1, $2, $3, $4, false, $5, $6) RETURNING id",
    [accountId, name, x, y, "{}", gameState]
  );
  return { id: res.rows[0].id };
}

/**
 * @param {string} zooId
 * @returns {Promise<{ id: string, name: string, x: number, y: number, is_bot: boolean, account_id: string | null, animal_weights: object, game_state: object | null } | null>}
 */
export async function getZooById(zooId) {
  const res = await pool.query(
    "SELECT id, name, x, y, is_bot, account_id, animal_weights, game_state FROM zoos WHERE id = $1",
    [zooId]
  );
  const row = res.rows[0];
  if (!row) return null;
  return {
    ...mapZooRowBase(row),
    is_bot: row.is_bot,
    account_id: row.account_id,
    animal_weights: row.animal_weights || {},
    game_state: row.game_state,
  };
}

/**
 * @param {string} zooId
 * @param {object} gameState
 * @returns {Promise<void>}
 */
export async function updateZooGameState(zooId, gameState) {
  await pool.query(
    "UPDATE zoos SET game_state = $1, updated_at = now() WHERE id = $2",
    [JSON.stringify(gameState), zooId]
  );
}

/**
 * @returns {Promise<number>}
 */
export async function countPlayerZoos() {
  const res = await pool.query(
    "SELECT COUNT(*) AS n FROM zoos WHERE is_bot = false"
  );
  return Number(res.rows[0]?.n) || 0;
}

/**
 * @param {number} x
 * @param {number} y
 * @param {object} animalWeights
 * @returns {Promise<string>} zoo id
 */
export async function createBotZoo(x, y, animalWeights) {
  const gameState = createInitialBotState();
  const res = await pool.query(
    "INSERT INTO zoos (account_id, name, x, y, is_bot, animal_weights, game_state) VALUES (NULL, $1, $2, $3, true, $4, $5) RETURNING id",
    [`Zoo bot ${x.toFixed(0)}-${y.toFixed(0)}`, x, y, JSON.stringify(animalWeights), JSON.stringify(gameState)]
  );
  return res.rows[0].id;
}

/**
 * Load bot zoos for server-side tick (id, name, x, y, animal_weights, game_state).
 * @returns {Promise<Array<{ id: string, name: string, x: number, y: number, animalWeights: object, botState: object }>>}
 */
export async function getBotZoosForTick() {
  const res = await pool.query(
    "SELECT id, name, x, y, animal_weights, game_state FROM zoos WHERE is_bot = true"
  );
  return res.rows.map((row) => ({
    ...mapZooRowBase(row),
    animalWeights: row.animal_weights || {},
    botState: row.game_state || createInitialBotState(),
  }));
}

/**
 * Persist bot zoo state after tick.
 * @param {string} zooId
 * @param {object} animalWeights
 * @param {object} gameState
 * @returns {Promise<void>}
 */
export async function updateBotZooState(zooId, animalWeights, gameState) {
  await pool.query(
    "UPDATE zoos SET animal_weights = $1, game_state = $2, updated_at = now() WHERE id = $3 AND is_bot = true",
    [JSON.stringify(animalWeights), JSON.stringify(gameState), zooId]
  );
}

export { pool };
