import express from "express";
import {
  getAccountByPublicKey,
  getZooByAccountId,
  getSalesForZoo,
  getActiveSaleListings,
  createSaleListing,
  placeBid,
  acceptSale,
  rejectSale,
  markSaleDelivered,
  expireSaleListings,
  processValidatedSales,
} from "../db.js";
import { verifySignature, buildSignMessage, hashBody } from "../auth.js";

const router = express.Router();
const TIMESTAMP_TOLERANCE_MS = 5 * 60 * 1000;

/** @param {string} timestamp
 * @returns {boolean}
 */
function isTimestampValid(timestamp) {
  const now = Date.now();
  const ts = new Date(timestamp).getTime();
  return !Number.isNaN(ts) && Math.abs(now - ts) <= TIMESTAMP_TOLERANCE_MS;
}

/** @param {import("express").Request} req
 * @returns {string}
 */
function getBodyForSignature(req) {
  return req.bodyRaw !== undefined && req.bodyRaw !== null ? req.bodyRaw : "";
}

/** @param {import("express").Request} req
 * @param {string} publicKey
 * @param {string} signature
 * @param {string} timestamp
 * @returns {boolean}
 */
function isSignatureValid(req, publicKey, signature, timestamp) {
  const bodyHash = hashBody(getBodyForSignature(req));
  const path = req.originalUrl || req.baseUrl + req.path || req.path;
  const message = buildSignMessage(req.method, path, timestamp, bodyHash);
  return verifySignature(publicKey, signature, message);
}

function requireSignature() {
  return (req, res, next) => {
    const publicKey = req.headers["x-public-key"];
    const signature = req.headers["x-signature"];
    const timestamp = req.headers["x-timestamp"];
    if (!publicKey || !signature || !timestamp) {
      res.status(401).json({ error: "Missing X-Public-Key, X-Signature, or X-Timestamp" });
      return;
    }
    if (!isTimestampValid(timestamp)) {
      res.status(401).json({ error: "Invalid or expired timestamp" });
      return;
    }
    if (!isSignatureValid(req, publicKey, signature, timestamp)) {
      res.status(401).json({ error: "Invalid signature" });
      return;
    }
    getAccountByPublicKey(publicKey).then((account) => {
      if (!account) {
        res.status(401).json({ error: "Unknown account" });
        return;
      }
      req.account = account;
      next();
    }).catch(next);
  };
}

function optionalSignature() {
  return (req, res, next) => {
    const publicKey = req.headers["x-public-key"];
    const signature = req.headers["x-signature"];
    const timestamp = req.headers["x-timestamp"];
    if (!publicKey || !signature || !timestamp) {
      next();
      return;
    }
    if (!isTimestampValid(timestamp) || !isSignatureValid(req, publicKey, signature, timestamp)) {
      next();
      return;
    }
    getAccountByPublicKey(publicKey).then((account) => {
      req.account = account ?? undefined;
      next();
    }).catch(next);
  };
}

/** GET /api/sales — optional auth: with auth returns asSeller, asBuyerUndelivered, active; without auth returns active only.
 * @param {import("express").Request} req
 * @returns {Promise<{ active: Array<object> } | { asSeller: Array<object>, asBuyerUndelivered: Array<object>, active: Array<object> }>}
 */
async function getSalesResponse(req) {
  await expireSaleListings();
  if (!req.account) {
    const active = await getActiveSaleListings();
    return { active };
  }
  await processValidatedSales();
  const zoo = await getZooByAccountId(req.account.id);
  if (!zoo) {
    return { asSeller: [], asBuyerUndelivered: [], active: await getActiveSaleListings() };
  }
  return getSalesForZoo(zoo.id);
}

router.get("/", optionalSignature(), async (req, res, next) => {
  try {
    const data = await getSalesResponse(req);
    res.json(data);
  } catch (e) {
    next(e);
  }
});

/**
 * @param {unknown} body
 * @returns {{ status: number, error: string } | null}
 */
function validateCreateListingBody(body) {
  const { animalId, isBaby, price, endAt } = body ?? {};
  if (typeof animalId !== "string" || !animalId.trim()) return { status: 400, error: "animalId required" };
  if (typeof isBaby !== "boolean") return { status: 400, error: "isBaby boolean required" };
  const initialPrice = Number(price);
  if (!Number.isFinite(initialPrice) || initialPrice < 0) return { status: 400, error: "price must be a non-negative number" };
  const endAtDate = endAt ? new Date(endAt) : null;
  if (!endAtDate || Number.isNaN(endAtDate.getTime())) return { status: 400, error: "endAt required (ISO date string)" };
  return null;
}

/**
 * @param {unknown} body
 * @returns {{ ok: true, animalId: string, isBaby: boolean, initialPrice: number, endAtDate: Date, reproductionScoreAtSale?: number } | { ok: false, status: number, error: string }}
 */
function parseCreateListingBody(body) {
  const err = validateCreateListingBody(body);
  if (err) return { ok: false, status: err.status, error: err.error };
  const { animalId, isBaby, price, endAt, reproductionScoreAtSale } = body ?? {};
  const endAtDate = endAt ? new Date(endAt) : null;
  const initialPrice = Number(price);
  const repScore = reproductionScoreAtSale !== null && reproductionScoreAtSale !== undefined ? Number(reproductionScoreAtSale) : undefined;
  return { ok: true, animalId: String(animalId).trim(), isBaby, initialPrice, endAtDate: /** @type {Date} */ (endAtDate), reproductionScoreAtSale: repScore };
}

/** POST /api/sales — create listing (auth). Body: { animalId, isBaby, price, endAt, reproductionScoreAtSale? }. */
router.post("/", requireSignature(), async (req, res, next) => {
  try {
    const zoo = await getZooByAccountId(req.account.id);
    if (!zoo) {
      res.status(404).json({ error: "No zoo for this account" });
      return;
    }
    const parsed = parseCreateListingBody(req.body);
    if (!parsed.ok) {
      res.status(parsed.status).json({ error: parsed.error });
      return;
    }
    const { id } = await createSaleListing({
      sellerZooId: zoo.id,
      animalId: parsed.animalId,
      isBaby: parsed.isBaby,
      initialPrice: parsed.initialPrice,
      endAt: parsed.endAtDate.toISOString(),
      reproductionScoreAtSale: parsed.reproductionScoreAtSale,
    });
    res.status(201).json({ id });
  } catch (e) {
    next(e);
  }
});

/** POST /api/sales/:id/bid — place bid (auth). Body: { amount }. */
router.post("/:id/bid", requireSignature(), async (req, res, next) => {
  try {
    const zoo = await getZooByAccountId(req.account.id);
    if (!zoo) {
      res.status(404).json({ error: "No zoo for this account" });
      return;
    }
    const amount = Number(req.body?.amount);
    if (!Number.isFinite(amount) || amount < 0) {
      res.status(400).json({ error: "amount must be a non-negative number" });
      return;
    }
    const result = await placeBid(req.params.id, zoo.id, amount);
    if (!result.ok) {
      res.status(400).json({ error: result.reason ?? "Bid failed" });
      return;
    }
    res.json({ ok: true });
  } catch (e) {
    next(e);
  }
});

/** POST /api/sales/:id/accept — seller accepts best bid (auth). */
router.post("/:id/accept", requireSignature(), async (req, res, next) => {
  try {
    const zoo = await getZooByAccountId(req.account.id);
    if (!zoo) {
      res.status(404).json({ error: "No zoo for this account" });
      return;
    }
    const result = await acceptSale(req.params.id, zoo.id);
    if (!result.ok) {
      res.status(400).json({ error: result.reason ?? "Accept failed" });
      return;
    }
    res.json({ ok: true });
  } catch (e) {
    next(e);
  }
});

/** POST /api/sales/:id/reject — seller rejects current best bid (auth). */
router.post("/:id/reject", requireSignature(), async (req, res, next) => {
  try {
    const zoo = await getZooByAccountId(req.account.id);
    if (!zoo) {
      res.status(404).json({ error: "No zoo for this account" });
      return;
    }
    const result = await rejectSale(req.params.id, zoo.id);
    if (!result.ok) {
      res.status(400).json({ error: result.reason ?? "Reject failed" });
      return;
    }
    res.json({ ok: true });
  } catch (e) {
    next(e);
  }
});

/** POST /api/sales/:id/deliver — buyer marks as delivered (auth). */
router.post("/:id/deliver", requireSignature(), async (req, res, next) => {
  try {
    const zoo = await getZooByAccountId(req.account.id);
    if (!zoo) {
      res.status(404).json({ error: "No zoo for this account" });
      return;
    }
    const result = await markSaleDelivered(req.params.id, zoo.id);
    if (!result.ok) {
      res.status(400).json({ error: result.reason ?? "Deliver failed" });
      return;
    }
    res.json({ ok: true });
  } catch (e) {
    next(e);
  }
});

export default router;
