/**
 * API client: signed requests (Ed25519), load/save zoo state and world zoos.
 */

import { getOrCreateKeyPair, signMessage } from "./auth-client.js";

/**
 * @returns {string} API base URL (from ?api=, window, or localStorage)
 */
export function getApiBase() {
  if (typeof window === "undefined") return "";
  let base = "";
  if (window.BUILAZOO_API_URL !== undefined && window.BUILAZOO_API_URL !== null && window.BUILAZOO_API_URL !== "") {
    base = String(window.BUILAZOO_API_URL);
  } else {
    try {
      const stored = localStorage.getItem("builazoo_api_url");
      if (stored && stored.trim() !== "") base = stored.trim();
    } catch (_) {
      // ignore localStorage access errors
    }
  }
  return base ? base.replace(/\/+$/, "") : "";
}

/**
 * Persist API base URL so it is used on next load (getApiBase reads it from localStorage).
 * @param {string} url
 */
export function setApiBaseUrl(url) {
  const raw = url ? String(url).trim() : "";
  const trimmed = raw ? raw.replace(/\/+$/, "") : "";
  if (typeof window !== "undefined") window.BUILAZOO_API_URL = trimmed || undefined;
  try {
    if (trimmed) localStorage.setItem("builazoo_api_url", trimmed);
    else localStorage.removeItem("builazoo_api_url");
  } catch (_) {
    // ignore localStorage errors
  }
}

/**
 * @param {string} body
 * @returns {Promise<string>} hex
 */
async function hashBody(body) {
  const enc = new TextEncoder().encode(body || "");
  const buf = await crypto.subtle.digest("SHA-256", enc);
  return Array.from(new Uint8Array(buf))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

/**
 * @param {string} method
 * @param {string} path
 * @param {string} [body]
 * @returns {Promise<Response>}
 */
export async function signedFetch(method, path, body) {
  const base = getApiBase();
  if (!base) throw new Error("No API base URL");
  const url = base + path;
  const timestamp = new Date().toISOString();
  const bodyStr = body !== null && body !== undefined ? (typeof body === "string" ? body : JSON.stringify(body)) : "";
  const bodyHash = await hashBody(bodyStr);
  const message = `${method}\n${path}\n${timestamp}\n${bodyHash}`;
  const keys = await getOrCreateKeyPair();
  if (!keys) throw new Error("No keypair");
  const signature = await signMessage(keys.privateKey, message);
  const headers = {
    "Content-Type": "application/json",
    "X-Public-Key": keys.publicKeyBase64,
    "X-Signature": signature,
    "X-Timestamp": timestamp,
  };
  const init = { method, headers };
  if (bodyStr) init.body = bodyStr;
  return fetch(url, init);
}

/**
 * @returns {Promise<{ worldZoos: Array<{ id: string, name: string, x: number, y: number, animalWeights: object }>, mapWidth: number, mapHeight: number }>}
 */
export async function loadZoos() {
  const base = getApiBase();
  if (!base) return { worldZoos: [], mapWidth: 100, mapHeight: 100 };
  const res = await fetch(base + "/api/zoos");
  if (!res.ok) throw new Error(`loadZoos ${res.status}`);
  const data = await res.json();
  return {
    worldZoos: data.worldZoos || [],
    mapWidth: data.mapWidth ?? 100,
    mapHeight: data.mapHeight ?? 100,
  };
}

/**
 * Load current user's zoo. Distinguishes 401 (account unknown → register) from 404 (no zoo → create zoo).
 * @returns {Promise<{ ok: true, data: { zooId: string, name: string, x: number, y: number, game_state: object | null } } | { status: 401 } | { status: 404 }>}
 */
export async function loadMyZoo() {
  const base = getApiBase();
  if (!base) return { status: 404 };
  const res = await signedFetch("GET", "/api/zoos/me");
  if (res.status === 401) return { status: 401 };
  if (res.status === 404) return { status: 404 };
  if (!res.ok) throw new Error(`loadMyZoo ${res.status}`);
  const data = await res.json();
  return { ok: true, data };
}

/**
 * @param {object} gameState
 * @returns {Promise<void>}
 */
export async function saveMyZoo(gameState) {
  const base = getApiBase();
  if (!base) return;
  const res = await signedFetch("PATCH", "/api/zoos/me", { game_state: gameState });
  if (!res.ok) throw new Error(`saveMyZoo ${res.status}`);
}

/**
 * @param {string} pseudo
 * @returns {Promise<{ id: string, pseudo: string }>}
 */
export async function register(pseudo) {
  const base = getApiBase();
  if (!base) throw new Error("No API base URL");
  const keys = await getOrCreateKeyPair();
  if (!keys) throw new Error("No keypair");
  const res = await fetch(base + "/api/auth/register", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Public-Key": keys.publicKeyBase64,
    },
    body: JSON.stringify({ pseudo: pseudo.trim() }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `register ${res.status}`);
  }
  return res.json();
}

/**
 * @param {string} name
 * @param {object} gameState
 * @returns {Promise<{ zooId: string, name: string, x: number, y: number }>}
 */
export async function createMyZoo(name, gameState) {
  const res = await signedFetch("POST", "/api/zoos/me", { name, game_state: gameState });
  if (!res.ok) throw new Error(`createMyZoo ${res.status}`);
  return res.json();
}

/**
 * Fetch sales: with auth returns { asSeller, asBuyerUndelivered, active }; without auth only { active }.
 * @returns {Promise<{ asSeller?: Array<object>, asBuyerUndelivered?: Array<object>, active: Array<object> }>}
 */
export async function getSales() {
  const base = getApiBase();
  if (!base) return { active: [] };
  const res = await signedFetch("GET", "/api/sales");
  if (!res.ok) throw new Error(`getSales ${res.status}`);
  return res.json();
}

/**
 * Map a server sale listing (asSeller item) to client SaleListing shape.
 * @param {{ id: string, seller_zoo_id: string, animal_id: string, is_baby: boolean, initial_price: number, end_at: string, status?: string, best_bid_amount?: number, best_bidder_zoo_id?: string, sold_at?: string, validated_at?: string | null, reproduction_score_at_sale?: number }} s
 * @returns {import("./types.js").SaleListing}
 */
export function mapServerListingToClient(s) {
  return {
    id: s.id,
    zooId: s.seller_zoo_id,
    animalId: s.animal_id,
    isBaby: s.is_baby,
    price: s.initial_price,
    endAt: Math.floor(new Date(s.end_at).getTime() / 1000),
    status: s.status,
    bestBidAmount: s.best_bid_amount,
    bestBidderZooId: s.best_bidder_zoo_id,
    validatedAt: s.validated_at ? Math.floor(new Date(s.validated_at).getTime() / 1000) : null,
    reproductionScoreAtSale: s.reproduction_score_at_sale,
    serverId: s.id,
  };
}

/**
 * Create a sale listing. Body: animalId, isBaby, price, endAt, reproductionScoreAtSale?.
 * @param {{ animalId: string, isBaby: boolean, price: number, endAt: string, reproductionScoreAtSale?: number }} payload
 * @returns {Promise<{ id: string }>}
 */
export async function createSale(payload) {
  const res = await signedFetch("POST", "/api/sales", payload);
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `createSale ${res.status}`);
  }
  return res.json();
}

/**
 * Place a bid on a listing.
 * @param {string} listingId
 * @param {number} amount
 * @returns {Promise<void>}
 */
export async function placeBid(listingId, amount) {
  const res = await signedFetch("POST", `/api/sales/${listingId}/bid`, { amount });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `placeBid ${res.status}`);
  }
}

/**
 * Seller accepts the best bid.
 * @param {string} listingId
 * @returns {Promise<void>}
 */
export async function acceptSale(listingId) {
  const res = await signedFetch("POST", `/api/sales/${listingId}/accept`);
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `acceptSale ${res.status}`);
  }
}

/**
 * Seller rejects the current best bid.
 * @param {string} listingId
 * @returns {Promise<void>}
 */
export async function rejectSale(listingId) {
  const res = await signedFetch("POST", `/api/sales/${listingId}/reject`);
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `rejectSale ${res.status}`);
  }
}

/**
 * Buyer marks a won listing as delivered (after adding baby/animal to zoo).
 * @param {string} listingId
 * @returns {Promise<void>}
 */
export async function deliverSale(listingId) {
  const res = await signedFetch("POST", `/api/sales/${listingId}/deliver`);
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `deliverSale ${res.status}`);
  }
}
