import { pool, getZooById, updateZooGameState } from "./db-core.js";

const SALE_STATUS = { ACTIVE: "active", SOLD: "sold", EXPIRED: "expired", REJECTED: "rejected", VALIDATED: "validated" };

/** Deferred validation delay in seconds (10 minutes). */
const SALE_VALIDATION_DELAY_SECONDS = 10 * 60;

/**
 * Map a sale_listings row to a listing object. Missing columns (e.g. sold_at on active-only SELECT) become undefined.
 * @param {Record<string, unknown>} row
 * @returns {{ id: string, seller_zoo_id: string, animal_id: string, is_baby: boolean, initial_price: number, end_at: Date, status: string, best_bid_amount: number | null, best_bidder_zoo_id: string | null, sold_at?: Date | null, validated_at?: Date | null, reproduction_score_at_sale: number | null, delivered_at?: Date | null, created_at?: Date }}
 */
function mapSaleListingRow(row) {
  return {
    id: row.id,
    seller_zoo_id: row.seller_zoo_id,
    animal_id: row.animal_id,
    is_baby: Boolean(row.is_baby),
    initial_price: Number(row.initial_price),
    end_at: row.end_at,
    status: String(row.status),
    best_bid_amount: (row.best_bid_amount !== null && row.best_bid_amount !== undefined) ? Number(row.best_bid_amount) : null,
    best_bidder_zoo_id: row.best_bidder_zoo_id ?? null,
    sold_at: row.sold_at ?? undefined,
    validated_at: row.validated_at ?? undefined,
    reproduction_score_at_sale: (row.reproduction_score_at_sale !== null && row.reproduction_score_at_sale !== undefined) ? Number(row.reproduction_score_at_sale) : null,
    delivered_at: row.delivered_at ?? undefined,
    created_at: row.created_at ?? undefined,
  };
}

/**
 * @param {{ sellerZooId: string, animalId: string, isBaby: boolean, initialPrice: number, endAt: string, reproductionScoreAtSale?: number }} opts
 * @returns {Promise<{ id: string }>}
 */
export async function createSaleListing(opts) {
  const { sellerZooId, animalId, isBaby, initialPrice, endAt, reproductionScoreAtSale } = opts;
  const res = await pool.query(
    `INSERT INTO sale_listings (seller_zoo_id, animal_id, is_baby, initial_price, end_at, reproduction_score_at_sale)
     VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
    [sellerZooId, animalId, isBaby, initialPrice, endAt, reproductionScoreAtSale ?? null]
  );
  return { id: res.rows[0].id };
}

/**
 * @param {string} listingId
 * @returns {Promise<{ id: string, seller_zoo_id: string, animal_id: string, is_baby: boolean, initial_price: number, end_at: Date, status: string, best_bid_amount: number | null, best_bidder_zoo_id: string | null, sold_at: Date | null, validated_at: Date | null, reproduction_score_at_sale: number | null, delivered_at: Date | null, created_at: Date } | null>}
 */
export async function getSaleListingById(listingId) {
  const res = await pool.query(
    "SELECT id, seller_zoo_id, animal_id, is_baby, initial_price, end_at, status, best_bid_amount, best_bidder_zoo_id, sold_at, validated_at, reproduction_score_at_sale, delivered_at, created_at FROM sale_listings WHERE id = $1",
    [listingId]
  );
  const row = res.rows[0];
  return row ? mapSaleListingRow(row) : null;
}

/**
 * Active listings (for marketplace).
 * @returns {Promise<Array<{ id: string, seller_zoo_id: string, animal_id: string, is_baby: boolean, initial_price: number, end_at: Date, status: string, best_bid_amount: number | null, best_bidder_zoo_id: string | null, reproduction_score_at_sale: number | null }>>}
 */
export async function getActiveSaleListings() {
  const res = await pool.query(
    `SELECT id, seller_zoo_id, animal_id, is_baby, initial_price, end_at, status, best_bid_amount, best_bidder_zoo_id, reproduction_score_at_sale
     FROM sale_listings WHERE status = $1 ORDER BY end_at ASC`,
    [SALE_STATUS.ACTIVE]
  );
  return res.rows.map(mapSaleListingRow);
}

/**
 * Listings relevant to a zoo: as seller (any status), as buyer (sold to me, not yet delivered), plus active for browsing.
 * @param {string} zooId
 * @returns {Promise<{ asSeller: Array<object>, asBuyerUndelivered: Array<object>, active: Array<object> }>}
 */
export async function getSalesForZoo(zooId) {
  const [sellerRes, buyerRes, activeRes] = await Promise.all([
    pool.query(
      `SELECT id, seller_zoo_id, animal_id, is_baby, initial_price, end_at, status, best_bid_amount, best_bidder_zoo_id, sold_at, validated_at, reproduction_score_at_sale, delivered_at, created_at
       FROM sale_listings WHERE seller_zoo_id = $1 ORDER BY created_at DESC`,
      [zooId]
    ),
    pool.query(
      `SELECT id, seller_zoo_id, animal_id, is_baby, initial_price, end_at, status, best_bid_amount, best_bidder_zoo_id, sold_at, validated_at, reproduction_score_at_sale, delivered_at, created_at
       FROM sale_listings WHERE best_bidder_zoo_id = $1 AND status = ANY($2::text[]) AND delivered_at IS NULL ORDER BY sold_at DESC`,
      [zooId, [SALE_STATUS.SOLD, SALE_STATUS.VALIDATED]]
    ),
    pool.query(
      `SELECT id, seller_zoo_id, animal_id, is_baby, initial_price, end_at, status, best_bid_amount, best_bidder_zoo_id, reproduction_score_at_sale
       FROM sale_listings WHERE status = $1 ORDER BY end_at ASC`,
      [SALE_STATUS.ACTIVE]
    ),
  ]);
  return {
    asSeller: sellerRes.rows.map(mapSaleListingRow),
    asBuyerUndelivered: buyerRes.rows.map(mapSaleListingRow),
    active: activeRes.rows.map(mapSaleListingRow),
  };
}

/**
 * @param {string} listingId
 * @param {string} sellerZooId
 * @returns {Promise<{ ok: true, listing: object } | { ok: false, reason: string }>}
 */
async function validateListingForSeller(listingId, sellerZooId) {
  const listing = await getSaleListingById(listingId);
  if (!listing) return { ok: false, reason: "ListingNotFound" };
  if (listing.status !== SALE_STATUS.ACTIVE) return { ok: false, reason: "ListingNotActive" };
  if (listing.seller_zoo_id !== sellerZooId) return { ok: false, reason: "NotSeller" };
  return { ok: true, listing };
}

/**
 * @param {object} listing
 * @returns {Promise<{ ok: true, buyerZoo: object, sellerZoo: object } | { ok: false, reason: string }>}
 */
async function validateBuyerAndSeller(listing) {
  const buyerZooId = listing.best_bidder_zoo_id;
  const amount = listing.best_bid_amount;
  if (!buyerZooId || amount === null || amount === undefined) return { ok: false, reason: "NoBid" };
  const buyerZoo = await getZooById(buyerZooId);
  const sellerZoo = await getZooById(listing.seller_zoo_id);
  if (!buyerZoo || !buyerZoo.game_state) return { ok: false, reason: "BuyerStateMissing" };
  if (!sellerZoo || !sellerZoo.game_state) return { ok: false, reason: "SellerStateMissing" };
  const buyerCoins = Number(buyerZoo.game_state.coins ?? 0);
  if (buyerCoins < amount) return { ok: false, reason: "BuyerInsufficientCoins" };
  return { ok: true, buyerZoo, sellerZoo };
}

/**
 * Seller accepts the current best bid: mark sold, set validated_at = now() + 10 minutes. Coins are transferred later by processValidatedSales().
 * @param {string} listingId
 * @param {string} sellerZooId
 * @returns {Promise<{ ok: boolean, reason?: string }>}
 */
export async function acceptSale(listingId, sellerZooId) {
  const validated = await validateListingForSeller(listingId, sellerZooId);
  if (!validated.ok) return { ok: false, reason: validated.reason };
  const parties = await validateBuyerAndSeller(validated.listing);
  if (!parties.ok) return { ok: false, reason: parties.reason };
  await pool.query(
    "UPDATE sale_listings SET status = $1, sold_at = now(), validated_at = now() + ($2::text || ' seconds')::interval WHERE id = $3",
    [SALE_STATUS.SOLD, String(SALE_VALIDATION_DELAY_SECONDS), listingId]
  );
  return { ok: true };
}
/**
 * Place or update bid for a listing. Only if listing is active and amount > current best_bid_amount (or initial_price).
 * @param {string} listingId
 * @param {string} bidderZooId
 * @param {number} amount
 * @returns {Promise<{ ok: boolean, reason?: string }>}
 */
export async function placeBid(listingId, bidderZooId, amount) {
  const listing = await getSaleListingById(listingId);
  if (!listing) return { ok: false, reason: "ListingNotFound" };
  if (listing.status !== SALE_STATUS.ACTIVE) return { ok: false, reason: "ListingNotActive" };
  const minAmount = listing.best_bid_amount ?? listing.initial_price;
  if (amount <= minAmount) return { ok: false, reason: "BidTooLow" };
  await pool.query(
    "INSERT INTO sale_bids (listing_id, bidder_zoo_id, amount) VALUES ($1, $2, $3) ON CONFLICT (listing_id, bidder_zoo_id) DO UPDATE SET amount = $3, created_at = now()",
    [listingId, bidderZooId, amount]
  );
  await pool.query(
    "UPDATE sale_listings SET best_bid_amount = $1, best_bidder_zoo_id = $2 WHERE id = $3",
    [amount, bidderZooId, listingId]
  );
  return { ok: true };
}

/**
 * Process sold listings whose validated_at <= now(): transfer coins (buyer -= amount, seller += amount), set status = 'validated'.
 * @returns {Promise<number>} count of listings processed
 */
export async function processValidatedSales() {
  const res = await pool.query(
    `SELECT id, seller_zoo_id, best_bidder_zoo_id, best_bid_amount FROM sale_listings
     WHERE status = $1 AND validated_at IS NOT NULL AND validated_at <= now()`,
    [SALE_STATUS.SOLD]
  );
  let count = 0;
  for (const row of res.rows) {
    const buyerZooId = row.best_bidder_zoo_id;
    const sellerZooId = row.seller_zoo_id;
    const amount = Number(row.best_bid_amount);
    if (buyerZooId && Number.isFinite(amount)) {
      const buyerZoo = await getZooById(buyerZooId);
      const sellerZoo = await getZooById(sellerZooId);
      if (buyerZoo?.game_state && sellerZoo?.game_state) {
        const buyerState = buyerZoo.game_state;
        const sellerState = sellerZoo.game_state;
        const buyerCoins = Number(buyerState.coins ?? 0);
        const sellerCoins = Number(sellerState.coins ?? 0);
        buyerState.coins = buyerCoins - amount;
        sellerState.coins = sellerCoins + amount;
        await updateZooGameState(buyerZooId, buyerState);
        await updateZooGameState(sellerZooId, sellerState);
        await pool.query(
          "UPDATE sale_listings SET status = $1 WHERE id = $2",
          [SALE_STATUS.VALIDATED, row.id]
        );
        count += 1;
      }
    }
  }
  return count;
}

/**
 * Seller rejects the sale (listing stays active; best bid is cleared so seller can accept a different bid later if any).
 * @param {string} listingId
 * @param {string} sellerZooId
 * @returns {Promise<{ ok: boolean, reason?: string }>}
 */
export async function rejectSale(listingId, sellerZooId) {
  const validated = await validateListingForSeller(listingId, sellerZooId);
  if (!validated.ok) return { ok: false, reason: validated.reason };
  await pool.query(
    "UPDATE sale_listings SET best_bid_amount = NULL, best_bidder_zoo_id = NULL WHERE id = $1",
    [listingId]
  );
  return { ok: true };
}

/**
 * Mark listing as delivered (buyer has applied it to their zoo).
 * @param {string} listingId
 * @param {string} buyerZooId
 * @returns {Promise<{ ok: boolean, reason?: string }>}
 */
export async function markSaleDelivered(listingId, buyerZooId) {
  const listing = await getSaleListingById(listingId);
  if (!listing) return { ok: false, reason: "ListingNotFound" };
  if (listing.status !== SALE_STATUS.VALIDATED) return { ok: false, reason: "NotValidated" };
  if (listing.best_bidder_zoo_id !== buyerZooId) return { ok: false, reason: "NotBuyer" };
  if (listing.delivered_at) return { ok: true };
  await pool.query("UPDATE sale_listings SET delivered_at = now() WHERE id = $1", [listingId]);
  return { ok: true };
}

/**
 * Expire active listings past end_at: set status=expired; if is_baby, increment seller's game_state.deathCountRecent.
 * @returns {Promise<number>} count of expired listings
 */
export async function expireSaleListings() {
  const res = await pool.query(
    `SELECT id, seller_zoo_id, is_baby FROM sale_listings
     WHERE status = $1 AND end_at < now()`,
    [SALE_STATUS.ACTIVE]
  );
  let count = 0;
  for (const row of res.rows) {
    await pool.query("UPDATE sale_listings SET status = $1 WHERE id = $2", [SALE_STATUS.EXPIRED, row.id]);
    if (row.is_baby) {
      const zoo = await getZooById(row.seller_zoo_id);
      if (zoo && zoo.game_state) {
        const state = zoo.game_state;
        state.deathCountRecent = (Number(state.deathCountRecent) || 0) + 1;
        await updateZooGameState(row.seller_zoo_id, state);
      }
    }
    count += 1;
  }
  return count;
}
