import { randomUUID } from "crypto";
import { db, timmyConfig, freeTierGrants, nostrIdentities, type NostrIdentity, type TrustTier } from "@workspace/db";
import { eq, sql } from "drizzle-orm";
import { makeLogger } from "./logger.js";
import { trustService } from "./trust.js";

const logger = makeLogger("free-tier");

// ── Env-var helpers ────────────────────────────────────────────────────────────

function envInt(name: string, fallback: number): number {
  const raw = parseInt(process.env[name] ?? "", 10);
  return Number.isFinite(raw) && raw >= 0 ? raw : fallback;
}

function envFloat(name: string, fallback: number): number {
  const raw = parseFloat(process.env[name] ?? "");
  return Number.isFinite(raw) && raw >= 0 ? raw : fallback;
}

// ── Daily free budget per trust tier (sats) ───────────────────────────────────
// Override via env vars. "new" tier gets 0 — must earn trust first.
const DAILY_BUDGET_NEW         = envInt("FREE_TIER_BUDGET_NEW",         0);
const DAILY_BUDGET_ESTABLISHED = envInt("FREE_TIER_BUDGET_ESTABLISHED", 50);
const DAILY_BUDGET_TRUSTED     = envInt("FREE_TIER_BUDGET_TRUSTED",     200);
const DAILY_BUDGET_ELITE       = envInt("FREE_TIER_BUDGET_ELITE",       1_000);

// ── Generosity pool ───────────────────────────────────────────────────────────
// Pool starts at POOL_INITIAL_SATS on first boot. Grows by POOL_CREDIT_PCT of
// every paid work invoice. Never drops below 0.
const POOL_INITIAL_SATS = envInt("FREE_TIER_POOL_INITIAL_SATS", 10_000);
const POOL_CREDIT_PCT   = envFloat("FREE_TIER_POOL_CREDIT_PCT",  10);
const POOL_KEY          = "generosity_pool_sats";

// ── Types ─────────────────────────────────────────────────────────────────────

export interface FreeTierDecision {
  serve:       "free" | "partial" | "gate";
  absorbSats:  number;
  chargeSats:  number;
}

// ── FreeTierService ───────────────────────────────────────────────────────────

export class FreeTierService {
  // Daily sat budget for each trust tier.
  dailyBudgetForTier(tier: TrustTier): number {
    switch (tier) {
      case "elite":       return DAILY_BUDGET_ELITE;
      case "trusted":     return DAILY_BUDGET_TRUSTED;
      case "established": return DAILY_BUDGET_ESTABLISHED;
      case "new":
      default:            return DAILY_BUDGET_NEW;
    }
  }

  // Read the current pool balance; initialise to POOL_INITIAL_SATS on first call.
  async getPoolBalance(): Promise<number> {
    const rows = await db
      .select()
      .from(timmyConfig)
      .where(eq(timmyConfig.key, POOL_KEY))
      .limit(1);

    if (rows[0]) return parseInt(rows[0].value, 10) || 0;

    // First boot — seed the pool
    await db
      .insert(timmyConfig)
      .values({ key: POOL_KEY, value: String(POOL_INITIAL_SATS) })
      .onConflictDoNothing();

    logger.info("generosity pool initialised", { initialSats: POOL_INITIAL_SATS });
    return POOL_INITIAL_SATS;
  }

  // How many sats has this identity absorbed today (resets after 24 h)?
  getTodayAbsorbed(identity: NostrIdentity): number {
    const hoursSinceReset =
      (Date.now() - identity.absorbedResetAt.getTime()) / (1000 * 60 * 60);
    if (hoursSinceReset >= 24) return 0;
    return identity.satsAbsorbedToday;
  }

  /**
   * Decide whether to serve a request for free, partially free, or at full cost.
   *
   * Returns:
   *   serve="free"    → absorbSats=estimatedSats, chargeSats=0
   *   serve="partial" → 0 < absorbSats < estimatedSats, chargeSats = remainder
   *   serve="gate"    → absorbSats=0, chargeSats=estimatedSats (pay full price)
   */
  async decide(pubkey: string | null, estimatedSats: number): Promise<FreeTierDecision> {
    const gate: FreeTierDecision = { serve: "gate", absorbSats: 0, chargeSats: estimatedSats };

    if (!pubkey || estimatedSats <= 0) return gate;

    const identity = await trustService.getIdentityWithDecay(pubkey);
    if (!identity) return gate;

    const dailyBudget = this.dailyBudgetForTier(identity.tier);
    if (dailyBudget === 0) return gate;

    const todayAbsorbed = this.getTodayAbsorbed(identity);
    const dailyRemaining = Math.max(0, dailyBudget - todayAbsorbed);
    if (dailyRemaining === 0) {
      logger.info("free-tier: daily budget exhausted", {
        pubkey: pubkey.slice(0, 8),
        tier: identity.tier,
        todayAbsorbed,
        dailyBudget,
      });
      return gate;
    }

    const poolBalance = await this.getPoolBalance();
    if (poolBalance <= 0) {
      logger.warn("free-tier: pool empty", { poolBalance });
      return gate;
    }

    const canAbsorb = Math.min(dailyRemaining, poolBalance, estimatedSats);

    if (canAbsorb >= estimatedSats) {
      logger.info("free-tier: serving free", {
        pubkey: pubkey.slice(0, 8),
        tier: identity.tier,
        estimatedSats,
        poolBalance,
      });
      return { serve: "free", absorbSats: estimatedSats, chargeSats: 0 };
    }

    if (canAbsorb > 0) {
      logger.info("free-tier: partial subsidy", {
        pubkey: pubkey.slice(0, 8),
        tier: identity.tier,
        absorbSats: canAbsorb,
        chargeSats: estimatedSats - canAbsorb,
      });
      return { serve: "partial", absorbSats: canAbsorb, chargeSats: estimatedSats - canAbsorb };
    }

    return gate;
  }

  /**
   * Credit the generosity pool from a paid interaction.
   * Called after a work invoice is paid and work completes successfully.
   * creditSats = paidSats * POOL_CREDIT_PCT / 100
   */
  async credit(paidSats: number): Promise<void> {
    if (paidSats <= 0) return;
    const creditSats = Math.floor(paidSats * POOL_CREDIT_PCT / 100);
    if (creditSats <= 0) return;

    const current = await this.getPoolBalance();
    const next = current + creditSats;
    const now = new Date();

    await db
      .insert(timmyConfig)
      .values({ key: POOL_KEY, value: String(next), updatedAt: now })
      .onConflictDoUpdate({
        target: timmyConfig.key,
        set: { value: String(next), updatedAt: now },
      });

    logger.info("generosity pool credited", { paidSats, creditSats, poolBalance: next });
  }

  /**
   * Record that Timmy absorbed `absorbSats` on behalf of `pubkey`.
   * Atomically: deducts from pool, increments identity's daily absorption, writes audit row.
   */
  async recordGrant(
    pubkey: string,
    requestHash: string,
    absorbSats: number,
  ): Promise<void> {
    if (absorbSats <= 0) return;

    const now = new Date();
    const identity = await trustService.getIdentity(pubkey);
    const todayAbsorbed = identity ? this.getTodayAbsorbed(identity) : 0;
    const isNewDay = identity
      ? (now.getTime() - identity.absorbedResetAt.getTime()) / (1000 * 60 * 60) >= 24
      : true;
    const newAbsorbed = isNewDay ? absorbSats : todayAbsorbed + absorbSats;

    // Atomically deduct from pool inside the transaction using a SQL expression.
    // GREATEST(..., 0) prevents the pool from going negative even under concurrency.
    // We read back the actual new value via RETURNING so the audit log is accurate.
    let actualNewPoolBalance = 0;

    await db.transaction(async (tx) => {
      // Ensure the pool row exists first (idempotent seed)
      await tx
        .insert(timmyConfig)
        .values({ key: POOL_KEY, value: String(POOL_INITIAL_SATS) })
        .onConflictDoNothing();

      // Atomically decrement using GREATEST to avoid going negative
      const updated = await tx
        .update(timmyConfig)
        .set({
          value: sql`GREATEST(value::int - ${absorbSats}, 0)::text`,
          updatedAt: now,
        })
        .where(eq(timmyConfig.key, POOL_KEY))
        .returning({ value: timmyConfig.value });

      actualNewPoolBalance = updated[0] ? parseInt(updated[0].value, 10) : 0;

      await tx
        .update(nostrIdentities)
        .set({
          satsAbsorbedToday: newAbsorbed,
          absorbedResetAt: isNewDay ? now : identity?.absorbedResetAt ?? now,
          updatedAt: now,
        })
        .where(eq(nostrIdentities.pubkey, pubkey));

      await tx.insert(freeTierGrants).values({
        id: randomUUID(),
        pubkey,
        requestHash,
        satsAbsorbed: absorbSats,
        poolBalanceAfter: actualNewPoolBalance,
      });
    });

    logger.info("free-tier grant recorded", {
      pubkey: pubkey.slice(0, 8),
      absorbSats,
      newPoolBalance: actualNewPoolBalance,
      newAbsorbed,
    });
  }

  /**
   * Pool status snapshot — for health/metrics endpoints.
   */
  async poolStatus(): Promise<{
    balanceSats: number;
    budgets: Record<TrustTier, number>;
  }> {
    const balanceSats = await this.getPoolBalance();
    return {
      balanceSats,
      budgets: {
        new:         DAILY_BUDGET_NEW,
        established: DAILY_BUDGET_ESTABLISHED,
        trusted:     DAILY_BUDGET_TRUSTED,
        elite:       DAILY_BUDGET_ELITE,
      },
    };
  }
}

export const freeTierService = new FreeTierService();
