import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.39.3";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, content-type, stripe-signature",
};

const encoder = new TextEncoder();

function timingSafeEqual(a: Uint8Array, b: Uint8Array) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
  return diff === 0;
}

function hexToBytes(hex: string) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
  return bytes;
}

async function verifyStripeSignature(rawBody: string, signatureHeader: string, secret: string) {
  const parts = signatureHeader.split(",").reduce<Record<string, string[]>>((acc, part) => {
    const [key, value] = part.split("=");
    if (!key || !value) return acc;
    acc[key] = [...(acc[key] || []), value];
    return acc;
  }, {});

  const timestamp = parts.t?.[0];
  const signatures = parts.v1 || [];
  if (!timestamp || signatures.length === 0) return false;

  const payload = `${timestamp}.${rawBody}`;
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const expected = new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(payload)));
  return signatures.some((sig) => timingSafeEqual(expected, hexToBytes(sig)));
}

function stripeHeaders(secretKey: string, accountId?: string | null) {
  return {
    Authorization: `Bearer ${secretKey}`,
    ...(accountId?.startsWith("acct_") ? { "Stripe-Account": accountId } : {}),
  };
}

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

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

  const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET");
  const sig = req.headers.get("stripe-signature");
  const rawBody = await req.text();

  if (webhookSecret && sig) {
    const valid = await verifyStripeSignature(rawBody, sig, webhookSecret);
    if (!valid) {
      console.error("signature verification failed");
      return new Response("signature error", { status: 400, headers: corsHeaders });
    }
  } else {
    console.warn("STRIPE_WEBHOOK_SECRET not set — accepting unverified event");
  }

  let event: any;
  try {
    event = JSON.parse(rawBody);
  } catch {
    return new Response("invalid body", { status: 400, headers: corsHeaders });
  }

  try {
    const type = event.type as string;
    const obj = event.data?.object || {};
    const piId: string | undefined =
      obj.object === "payment_intent" ? obj.id :
      obj.payment_intent || obj.charge?.payment_intent;

    if (!piId) {
      console.log("webhook: no payment_intent on event", type);
      return new Response(JSON.stringify({ ok: true, ignored: "no PI" }), {
        status: 200,
        headers: { ...corsHeaders, "Content-Type": "application/json" },
      });
    }

    let { data: payment } = await supabase
      .from("stripe_payments")
      .select("*")
      .eq("stripe_payment_intent_id", piId)
      .maybeSingle();

    const eventAccount = event.account || obj.account || null;
    const metadata = obj.metadata || {};

    let mappingQuery = supabase
      .from("stripe_account_mappings")
      .select("stripe_secret_key, stripe_account_id, association_id")
      .eq("is_active", true);

    if (payment?.association_id) {
      mappingQuery = mappingQuery.eq("association_id", payment.association_id);
    } else if (metadata.association_id) {
      mappingQuery = mappingQuery.eq("association_id", metadata.association_id);
    } else if (eventAccount) {
      mappingQuery = mappingQuery.eq("stripe_account_id", eventAccount);
    } else {
      mappingQuery = mappingQuery.limit(1);
    }

    const { data: mapping } = await mappingQuery.maybeSingle();
    if (!mapping?.stripe_secret_key) {
      console.error("webhook: no stripe key for", { piId, eventAccount, associationId: payment?.association_id || metadata.association_id });
      return new Response("no stripe key", { status: 400, headers: corsHeaders });
    }

    const stripeFetch = async (path: string) => {
      const r = await fetch(`https://api.stripe.com/v1${path}`, {
        headers: stripeHeaders(mapping.stripe_secret_key, mapping.stripe_account_id),
      });
      if (!r.ok) console.error("Stripe fetch failed", path, await r.text());
      return r.ok ? await r.json() : null;
    };

    const pi = await stripeFetch(`/payment_intents/${piId}`);
    const piMetadata = pi?.metadata || metadata;

    if (!payment && pi && (piMetadata.association_id || mapping.association_id)) {
      const amountCents = Number(piMetadata.net_amount_cents || pi.amount || 0);
      const feeCents = Number(piMetadata.fee_cents || Math.max(Number(pi.amount || 0) - amountCents, 0));
      const { data: insertedPayment, error: paymentInsertError } = await supabase
        .from("stripe_payments")
        .insert({
          association_id: piMetadata.association_id || mapping.association_id,
          owner_id: piMetadata.owner_id || null,
          unit_id: piMetadata.unit_id || null,
          stripe_payment_intent_id: piId,
          amount_cents: amountCents,
          fee_cents: feeCents,
          total_cents: Number(pi.amount || amountCents + feeCents),
          payment_method_type: pi.payment_method_types?.includes("us_bank_account") ? "us_bank_account" : "card",
          status: pi.status || "pending",
          description: pi.description || obj.description || "HOA Assessment Payment",
        })
        .select("*")
        .single();

      if (paymentInsertError) throw paymentInsertError;
      payment = insertedPayment;
    }

    if (!payment) {
      console.log("webhook: no stripe_payments row for", piId);
      return new Response(JSON.stringify({ ok: true, ignored: "unknown PI" }), {
        status: 200,
        headers: { ...corsHeaders, "Content-Type": "application/json" },
      });
    }

    const today = new Date().toISOString().split("T")[0];

    const postPaymentToLedger = async (newStatus: string) => {
      await supabase.from("stripe_payments")
        .update({ status: newStatus, updated_at: new Date().toISOString() })
        .eq("id", payment.id);

      const { data: existing } = await supabase
        .from("owner_ledger_entries")
        .select("id")
        .eq("reference_type", "stripe_payment")
        .eq("reference_id", piId)
        .maybeSingle();

      if (existing) return { skipped: true };
      if (!payment.owner_id) return { skipped: "no owner" };

      const netAmount = Number(payment.amount_cents) / 100;
      const methodLabel = payment.payment_method_type === "us_bank_account" ? "ACH" : "Card";
      const { error } = await supabase.from("owner_ledger_entries").insert({
        owner_id: payment.owner_id,
        association_id: payment.association_id,
        unit_id: payment.unit_id,
        date: today,
        transaction_type: "payment",
        description: `Online Payment (${methodLabel}) — ${payment.description || "Assessment"}`,
        debit: 0,
        credit: netAmount,
        reference_type: "stripe_payment",
        reference_id: piId,
      });

      if (error) throw error;
      return { recorded: true };
    };

    const reverseLedger = async (refId: string, amount: number, description: string, newStatus: string) => {
      const { data: existing } = await supabase
        .from("owner_ledger_entries")
        .select("id")
        .eq("reference_type", "stripe_payment_reversal")
        .eq("reference_id", refId)
        .maybeSingle();

      if (existing) return { skipped: true };
      if (!payment.owner_id) return { skipped: "no owner" };

      const { error } = await supabase.from("owner_ledger_entries").insert({
        owner_id: payment.owner_id,
        association_id: payment.association_id,
        unit_id: payment.unit_id,
        date: today,
        transaction_type: "adjustment",
        description,
        debit: amount,
        credit: 0,
        reference_type: "stripe_payment_reversal",
        reference_id: refId,
      });

      if (error) throw error;
      await supabase.from("stripe_payments")
        .update({ status: newStatus, updated_at: new Date().toISOString() })
        .eq("id", payment.id);
      return { recorded: true };
    };

    if (type === "payment_intent.succeeded" || type === "payment_intent.processing") {
      if (!pi) return new Response("PI fetch failed", { status: 502, headers: corsHeaders });
      if (pi.status === "succeeded" || pi.status === "processing") await postPaymentToLedger(pi.status);
    } else if (type === "charge.refunded") {
      const charge = await stripeFetch(`/charges/${obj.id}?expand[]=refunds`);
      if (!charge) return new Response("charge fetch failed", { status: 502, headers: corsHeaders });
      for (const r of charge.refunds?.data || []) {
        if (r.status !== "succeeded") continue;
        await reverseLedger(
          `${piId}:refund:${r.id}`,
          Number(r.amount) / 100,
          `Refund — Stripe payment (${r.id})`,
          charge.amount_refunded >= charge.amount ? "refunded" : "partially_refunded",
        );
      }
    } else if (type === "payment_intent.payment_failed" || type === "charge.failed") {
      if (!pi) return new Response("PI fetch failed", { status: 502, headers: corsHeaders });
      const { data: priorCredit } = await supabase
        .from("owner_ledger_entries")
        .select("id, credit")
        .eq("reference_type", "stripe_payment")
        .eq("reference_id", piId)
        .maybeSingle();

      if (priorCredit) {
        await reverseLedger(
          `${piId}:failed`,
          Number(priorCredit.credit) || (Number(payment.amount_cents) / 100),
          `Returned/Failed Payment — ${pi.last_payment_error?.message || obj.failure_message || "Payment failed / returned"}`,
          "failed",
        );
      } else {
        await supabase.from("stripe_payments")
          .update({ status: "failed", updated_at: new Date().toISOString() })
          .eq("id", payment.id);
      }
    } else if (type === "charge.dispute.created") {
      await reverseLedger(
        `${piId}:dispute:${obj.id}`,
        Number(obj.amount || 0) / 100,
        `Chargeback dispute opened — ${obj.reason || "unspecified"}`,
        "disputed",
      );
    }

    return new Response(JSON.stringify({ ok: true, type }), {
      status: 200,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  } catch (err) {
    console.error("stripe-webhook error:", err);
    return new Response(JSON.stringify({ error: (err as Error).message }), {
      status: 500,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
});
