// Auto-applies late fees and interest based on association_fee_rules.
// Idempotent: skips owners that already received the same fee type within the current period.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.99.1";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};

type FeeExclusion = {
  fee_type: "late_fee" | "interest";
  mode: "waive" | "override_amount" | "override_percent";
  override_amount: number | null;
  override_percent: number | null;
};

function periodKey(today: Date, schedule: string): string {
  const y = today.getUTCFullYear();
  const m = today.getUTCMonth();
  if (schedule === "quarterly") return `${y}-Q${Math.floor(m / 3) + 1}`;
  if (schedule === "annual") return `${y}`;
  return `${y}-${String(m + 1).padStart(2, "0")}`;
}

function periodWindow(today: Date, schedule: string): { start: string; end: string } {
  const y = today.getUTCFullYear();
  const m = today.getUTCMonth();
  let startMonth = m;
  let monthsInPeriod = 1;
  if (schedule === "quarterly") {
    startMonth = Math.floor(m / 3) * 3;
    monthsInPeriod = 3;
  } else if (schedule === "annual") {
    startMonth = 0;
    monthsInPeriod = 12;
  }
  const start = new Date(Date.UTC(y, startMonth, 1)).toISOString().slice(0, 10);
  const end = new Date(Date.UTC(y, startMonth + monthsInPeriod, 0)).toISOString().slice(0, 10);
  return { start, end };
}

function resolveAmount(requested: number, exclusion: FeeExclusion | null, balance: number): number {
  if (!(requested > 0)) return 0;
  if (!exclusion) return requested;
  if (exclusion.mode === "waive") return 0;
  if (exclusion.mode === "override_amount") {
    const ov = Number(exclusion.override_amount || 0);
    return ov > 0 ? Number(ov.toFixed(2)) : 0;
  }
  if (exclusion.mode === "override_percent" && exclusion.override_percent != null) {
    const pct = Number(exclusion.override_percent); // stored as fraction (e.g. 0.015)
    if (pct <= 0 || balance <= 0) return 0;
    return Number((balance * pct).toFixed(2));
  }
  return requested;
}

Deno.serve(async (req) => {
  if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });

  try {
    const supabase = createClient(
      Deno.env.get("SUPABASE_URL")!,
      Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
    );

    const url = new URL(req.url);
    const dryRun = url.searchParams.get("dry_run") === "1";
    const forceAssoc = url.searchParams.get("association_id");
    const force = url.searchParams.get("force") === "1";

    const today = new Date();
    const todayISO = today.toISOString().slice(0, 10);
    const todayDay = today.getUTCDate();

    // 1. Load all enabled rules
    let rulesQuery = supabase
      .from("association_fee_rules")
      .select("*")
      .eq("auto_apply_enabled", true);
    if (forceAssoc) rulesQuery = rulesQuery.eq("association_id", forceAssoc);
    const { data: rules, error: rulesErr } = await rulesQuery;
    if (rulesErr) throw rulesErr;

    const eligibleRules = (rules || []).filter((r: any) => {
      if (!r.late_fee_enabled && !r.interest_enabled) return false;
      if (force) return true;
      // Only run when today >= configured day in current period
      const day = Number(r.auto_apply_day || 1);
      if (todayDay < day) return false;
      // Only run for current month boundaries based on schedule
      const sched = r.auto_apply_schedule || "monthly";
      if (sched === "quarterly" && today.getUTCMonth() % 3 !== 0) return false;
      if (sched === "annual" && today.getUTCMonth() !== 0) return false;
      return true;
    });

    const summary: any[] = [];
    let totalLateFees = 0;
    let totalInterest = 0;

    for (const rule of eligibleRules) {
      const associationId = rule.association_id;
      const sched = rule.auto_apply_schedule || "monthly";
      const { start: periodStart, end: periodEnd } = periodWindow(today, sched);

      // 2. Owners + units
      const { data: owners, error: ownersErr } = await supabase
        .from("owners")
        .select("id, unit_id, association_id, status")
        .eq("association_id", associationId)
        .eq("status", "active");
      if (ownersErr) {
        summary.push({ associationId, error: ownersErr.message });
        continue;
      }
      const ownerIds = (owners || []).map((o: any) => o.id);
      if (ownerIds.length === 0) {
        summary.push({ associationId, posted: 0, note: "no active owners" });
        continue;
      }
      const ownerById = new Map<string, any>();
      (owners || []).forEach((o: any) => ownerById.set(o.id, o));

      // 3. Unit fee exclusions
      const { data: exclusions } = await supabase
        .from("unit_fee_exclusions")
        .select("unit_id, fee_type, mode, override_amount, override_percent")
        .eq("association_id", associationId);
      const exByUnit: Record<string, { late_fee?: FeeExclusion; interest?: FeeExclusion }> = {};
      (exclusions || []).forEach((e: any) => {
        if (!e.unit_id) return;
        if (!exByUnit[e.unit_id]) exByUnit[e.unit_id] = {};
        exByUnit[e.unit_id][e.fee_type as "late_fee" | "interest"] = e;
      });

      // 4. Pull all ledger entries for these owners (paginated)
      const ledgerByOwner: Record<string, any[]> = {};
      const PAGE = 1000;
      // chunk owner ids to avoid URL bloat
      const chunkSize = 100;
      for (let i = 0; i < ownerIds.length; i += chunkSize) {
        const chunk = ownerIds.slice(i, i + chunkSize);
        let from = 0;
        while (true) {
          const { data, error } = await supabase
            .from("owner_ledger_entries")
            .select("owner_id, transaction_type, debit, credit, date")
            .in("owner_id", chunk)
            .order("date", { ascending: true })
            .range(from, from + PAGE - 1);
          if (error) throw error;
          (data || []).forEach((e: any) => {
            if (!ledgerByOwner[e.owner_id]) ledgerByOwner[e.owner_id] = [];
            ledgerByOwner[e.owner_id].push(e);
          });
          if (!data || data.length < PAGE) break;
          from += PAGE;
        }
      }

      const inserts: any[] = [];
      let assocLate = 0;
      let assocInterest = 0;
      let skippedAlreadyPosted = 0;
      let skippedNoBalance = 0;
      let waived = 0;

      for (const owner of owners || []) {
        const entries = ledgerByOwner[owner.id] || [];
        let balance = 0;
        let lastPaymentDate: string | null = null;
        let postedLateThisPeriod = false;
        let postedInterestThisPeriod = false;

        for (const e of entries) {
          balance += (Number(e.debit) || 0) - (Number(e.credit) || 0);
          const t = (e.transaction_type || "").toLowerCase();
          const d = e.date as string | null;
          if (!d) continue;
          if ((t.includes("payment") || (Number(e.credit) > 0 && !t)) && (!lastPaymentDate || d > lastPaymentDate)) {
            lastPaymentDate = d;
          }
          if (d >= periodStart && d <= periodEnd) {
            if (t.includes("late")) postedLateThisPeriod = true;
            if (t.includes("interest")) postedInterestThisPeriod = true;
          }
        }

        if (balance <= 0) {
          skippedNoBalance++;
          continue;
        }

        const unitId = owner.unit_id;
        const unitEx = unitId ? exByUnit[unitId] || {} : {};

        // ---------- Late fee ----------
        if (rule.late_fee_enabled && !postedLateThisPeriod) {
          // Determine if past trigger
          const triggerDays = Number(rule.late_fee_trigger_days || 0);
          let triggered = false;
          if (lastPaymentDate) {
            const ageMs = today.getTime() - new Date(lastPaymentDate + "T12:00:00Z").getTime();
            const ageDays = Math.floor(ageMs / 86400000);
            triggered = ageDays >= triggerDays;
          } else {
            triggered = true; // never paid, has balance
          }
          // If non-recurring, only post once ever
          if (triggered && !rule.late_fee_recurring) {
            const everPostedLate = entries.some((e) => (e.transaction_type || "").toLowerCase().includes("late"));
            if (everPostedLate) triggered = false;
          }
          if (triggered) {
            let requested = 0;
            if ((rule.late_fee_type || "flat") === "percentage") {
              const pct = Number(rule.late_fee_amount || 0);
              requested = Number((balance * pct / 100).toFixed(2));
              if (rule.late_fee_max && requested > Number(rule.late_fee_max)) requested = Number(rule.late_fee_max);
            } else {
              requested = Number(rule.late_fee_amount || 0);
            }
            const exc = (unitEx.late_fee as FeeExclusion | undefined) || null;
            const amount = resolveAmount(requested, exc, balance);
            if (amount > 0) {
              inserts.push({
                association_id: associationId,
                owner_id: owner.id,
                unit_id: unitId,
                date: todayISO,
                transaction_type: "late_fee",
                description: exc?.mode === "override_amount"
                  ? "Late fee (override per unit exclusion) — auto"
                  : "Late fee (auto-applied)",
                debit: amount,
                credit: 0,
              });
              assocLate += amount;
            } else if (exc?.mode === "waive") {
              waived++;
            }
          }
        } else if (postedLateThisPeriod) {
          skippedAlreadyPosted++;
        }

        // ---------- Interest ----------
        if (rule.interest_enabled && !postedInterestThisPeriod) {
          const graceDays = Number(rule.interest_grace_days || 0);
          let triggered = false;
          if (lastPaymentDate) {
            const ageMs = today.getTime() - new Date(lastPaymentDate + "T12:00:00Z").getTime();
            const ageDays = Math.floor(ageMs / 86400000);
            triggered = ageDays >= graceDays;
          } else {
            triggered = true;
          }
          if (triggered) {
            const annualRate = Number(rule.interest_rate || 0);
            const compound = (rule.interest_compound || "monthly").toLowerCase();
            // interest_rate is stored as APR percent. monthly = APR/12, simple/monthly schedule both apply once per period.
            let periodicRate = annualRate / 100;
            if (compound === "monthly" || compound === "simple") periodicRate = annualRate / 100 / 12;
            else if (compound === "quarterly") periodicRate = annualRate / 100 / 4;
            else if (compound === "daily") periodicRate = annualRate / 100 / 365 * 30;
            const requested = Number((balance * periodicRate).toFixed(2));
            const exc = (unitEx.interest as FeeExclusion | undefined) || null;
            const amount = resolveAmount(requested, exc, balance);
            if (amount > 0) {
              inserts.push({
                association_id: associationId,
                owner_id: owner.id,
                unit_id: unitId,
                date: todayISO,
                transaction_type: "interest",
                description: exc?.mode === "override_amount"
                  ? "Interest (override per unit exclusion) — auto"
                  : exc?.mode === "override_percent"
                  ? `Interest @ ${(Number(exc.override_percent) * 100).toFixed(2)}% (per unit override) — auto`
                  : `Interest @ ${annualRate}% per annum — auto`,
                debit: amount,
                credit: 0,
              });
              assocInterest += amount;
            } else if (exc?.mode === "waive") {
              waived++;
            }
          }
        } else if (postedInterestThisPeriod) {
          skippedAlreadyPosted++;
        }
      }

      let posted = 0;
      if (inserts.length && !dryRun) {
        // batch in chunks of 500
        for (let i = 0; i < inserts.length; i += 500) {
          const batch = inserts.slice(i, i + 500);
          const { error: insErr } = await supabase.from("owner_ledger_entries").insert(batch);
          if (insErr) {
            summary.push({ associationId, error: insErr.message, posted });
            break;
          }
          posted += batch.length;
        }
      } else {
        posted = inserts.length;
      }

      totalLateFees += assocLate;
      totalInterest += assocInterest;

      summary.push({
        associationId,
        period: periodKey(today, sched),
        posted,
        late_fees_total: assocLate,
        interest_total: assocInterest,
        waived_by_exclusion: waived,
        skipped_already_posted: skippedAlreadyPosted,
        skipped_no_balance: skippedNoBalance,
      });
    }

    return new Response(
      JSON.stringify({
        ok: true,
        dry_run: dryRun,
        rules_processed: eligibleRules.length,
        total_late_fees: totalLateFees,
        total_interest: totalInterest,
        results: summary,
      }),
      { headers: { ...corsHeaders, "Content-Type": "application/json" } },
    );
  } catch (err) {
    console.error("post-recurring-fees error:", err);
    return new Response(JSON.stringify({ error: (err as Error).message }), {
      status: 500,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
});
