// Buildium Import Apply — applies approved staged rows from buildium_import_staging
// to live tables in dependency order (units → owners → gl_account → ledger_entry).
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1";

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

const KIND_ORDER = ["unit", "owner", "gl_account", "ledger_entry", "arc_application"] as const;
const BUILDIUM_BASE = "https://api.buildium.com";

async function buildiumFetch(path: string, clientId: string, clientSecret: string): Promise<Response> {
  return fetch(`${BUILDIUM_BASE}${path}`, {
    headers: {
      "x-buildium-client-id": clientId,
      "x-buildium-client-secret": clientSecret,
      Accept: "application/json",
    },
  });
}

function stripPrivate(p: Record<string, any>): Record<string, any> {
  const out: Record<string, any> = {};
  for (const [k, v] of Object.entries(p)) if (!k.startsWith("_")) out[k] = v;
  return out;
}

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

  try {
    const authHeader = req.headers.get("Authorization");
    if (!authHeader?.startsWith("Bearer ")) {
      return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } });
    }
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const anonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
    const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    const token = authHeader.replace("Bearer ", "");

    let userId: string | null = null;
    try {
      const payload = token.split(".")[1];
      const padded = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "=");
      userId = JSON.parse(atob(padded))?.sub ?? null;
    } catch {
      return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } });
    }

    const auth = createClient(supabaseUrl, anonKey, { global: { headers: { Authorization: authHeader } } });
    const { data: roles } = await auth.from("user_roles").select("role").eq("user_id", userId);
    const isStaff = (roles || []).some((r: any) => r.role === "admin" || r.role === "manager");
    if (!isStaff) {
      return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } });
    }

    const supabase = createClient(supabaseUrl, serviceKey);
    const body = await req.json().catch(() => ({}));
    const batchId: string | null = typeof body.batch_id === "string" ? body.batch_id : null;
    const stagingIds: string[] | null = Array.isArray(body.staging_ids) ? body.staging_ids.filter((s: any) => typeof s === "string") : null;

    if (!batchId && (!stagingIds || stagingIds.length === 0)) {
      return new Response(JSON.stringify({ error: "Provide batch_id or staging_ids" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } });
    }

    // Fetch approved staged rows
    let q = supabase.from("buildium_import_staging").select("*").eq("status", "approved");
    if (batchId) q = q.eq("batch_id", batchId);
    if (stagingIds && stagingIds.length > 0) q = q.in("id", stagingIds);
    const { data: staged, error: stagedErr } = await q;
    if (stagedErr) throw stagedErr;

    if (!staged || staged.length === 0) {
      return new Response(JSON.stringify({ success: true, applied: 0, failed: 0, message: "No approved rows to apply" }), {
        status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" },
      });
    }

    // Group by kind
    const byKind: Record<string, any[]> = {};
    for (const k of KIND_ORDER) byKind[k] = [];
    for (const r of staged) if (KIND_ORDER.includes(r.kind)) byKind[r.kind].push(r);

    let applied = 0, failed = 0;
    const nowIso = new Date().toISOString();

    async function markApplied(id: string) {
      await supabase.from("buildium_import_staging").update({
        status: "applied", applied_at: nowIso, apply_error: null,
      }).eq("id", id);
      applied++;
    }
    async function markFailed(id: string, msg: string) {
      await supabase.from("buildium_import_staging").update({
        status: "failed", apply_error: msg.slice(0, 1000),
      }).eq("id", id);
      failed++;
    }

    // ---- 1. UNITS ----
    const newBuildiumUnitToLocalId = new Map<string, string>();
    for (const row of byKind.unit) {
      try {
        const p = stripPrivate(row.payload || {});
        if (row.action === "update" && row.match_id) {
          const { error } = await supabase.from("units").update(p).eq("id", row.match_id);
          if (error) throw error;
          if (p.buildium_unit_id) newBuildiumUnitToLocalId.set(String(p.buildium_unit_id), row.match_id);
        } else {
          const { data: ins, error } = await supabase.from("units").insert(p).select("id").single();
          if (error) throw error;
          if (p.buildium_unit_id) newBuildiumUnitToLocalId.set(String(p.buildium_unit_id), ins.id);
        }
        await markApplied(row.id);
      } catch (e: any) {
        await markFailed(row.id, e?.message || String(e));
      }
    }

    // Build unit lookup for downstream owner/ledger resolution
    const { data: allUnits } = await supabase.from("units").select("id, buildium_unit_id").not("buildium_unit_id", "is", null);
    const unitByBuildium = new Map<string, string>();
    for (const u of allUnits || []) unitByBuildium.set(String(u.buildium_unit_id), u.id);

    // ---- 2. OWNERS ----
    for (const row of byKind.owner) {
      try {
        const p = { ...(row.payload || {}) };
        // Resolve unit_id by buildium id if needed
        if (!p.unit_id && p._resolve_unit_buildium_id) {
          const localUnit = unitByBuildium.get(String(p._resolve_unit_buildium_id));
          if (localUnit) p.unit_id = localUnit;
        }
        const clean = stripPrivate(p);
        if (row.action === "update" && row.match_id) {
          const { error } = await supabase.from("owners").update(clean).eq("id", row.match_id);
          if (error) throw error;
        } else {
          const { error } = await supabase.from("owners").insert(clean);
          if (error) throw error;
        }
        await markApplied(row.id);
      } catch (e: any) {
        await markFailed(row.id, e?.message || String(e));
      }
    }

    // Build owner lookup
    const { data: allOwners } = await supabase.from("owners").select("id, buildium_owner_id, unit_id").not("buildium_owner_id", "is", null);
    const ownerByBuildium = new Map<string, { id: string; unit_id: string | null }>();
    for (const o of allOwners || []) ownerByBuildium.set(String(o.buildium_owner_id), { id: o.id, unit_id: o.unit_id });

    // ---- 3. GL ACCOUNTS (parents first) ----
    const glRows = byKind.gl_account.slice().sort((a, b) => {
      const ap = a.payload?._parent_buildium_id ? 1 : 0;
      const bp = b.payload?._parent_buildium_id ? 1 : 0;
      return ap - bp;
    });
    // Need a per-association lookup of account_number -> id for parent linkage
    const acctNumToIdByAssoc = new Map<string, Map<string, string>>();
    async function getAcctMap(assocId: string) {
      let m = acctNumToIdByAssoc.get(assocId);
      if (!m) {
        const { data } = await supabase.from("chart_of_accounts").select("id, account_number").eq("association_id", assocId);
        m = new Map<string, string>();
        for (const r of data || []) m.set(String(r.account_number), r.id);
        acctNumToIdByAssoc.set(assocId, m);
      }
      return m;
    }
    for (const row of glRows) {
      try {
        const p = { ...(row.payload || {}) };
        if (p._parent_buildium_id && row.association_id) {
          const m = await getAcctMap(row.association_id);
          const parentId = m.get(String(p._parent_buildium_id));
          if (parentId) p.parent_account_id = parentId;
        }
        const clean = stripPrivate(p);
        if (row.action === "update" && row.match_id) {
          const { error } = await supabase.from("chart_of_accounts").update(clean).eq("id", row.match_id);
          if (error) throw error;
        } else {
          const { data: ins, error } = await supabase.from("chart_of_accounts").upsert(clean, { onConflict: "association_id, account_number" }).select("id, account_number").single();
          if (error) throw error;
          if (row.association_id && ins) {
            const m = await getAcctMap(row.association_id);
            m.set(String(ins.account_number), ins.id);
          }
        }
        await markApplied(row.id);
      } catch (e: any) {
        await markFailed(row.id, e?.message || String(e));
      }
    }

    // ---- 4. LEDGER ENTRIES ----
    for (const row of byKind.ledger_entry) {
      try {
        const p = { ...(row.payload || {}) };
        if (!p.unit_id && p._resolve_unit_buildium_id) {
          const u = unitByBuildium.get(String(p._resolve_unit_buildium_id));
          if (u) p.unit_id = u;
        }
        if (!p.owner_id && Array.isArray(p._resolve_owner_buildium_ids)) {
          for (const boid of p._resolve_owner_buildium_ids) {
            const o = ownerByBuildium.get(String(boid));
            if (o) { p.owner_id = o.id; break; }
          }
        }
        // Fallback: any owner attached to this unit
        if (!p.owner_id && p.unit_id) {
          const { data: anyOwner } = await supabase.from("owners").select("id").eq("unit_id", p.unit_id).limit(1).maybeSingle();
          if (anyOwner) p.owner_id = anyOwner.id;
        }
        if (!p.unit_id || !p.owner_id) {
          throw new Error("Could not resolve unit/owner for ledger entry");
        }
        const clean = stripPrivate(p);
        // Skip if a buildium ref with same unit_id+reference_id already exists
        const { data: dupe } = await supabase
          .from("owner_ledger_entries")
          .select("id").eq("unit_id", clean.unit_id).eq("reference_type", "buildium").eq("reference_id", clean.reference_id)
          .maybeSingle();
        if (dupe) {
          await markApplied(row.id);
          continue;
        }
        const { error } = await supabase.from("owner_ledger_entries").insert(clean);
        if (error) throw error;
        await markApplied(row.id);
      } catch (e: any) {
        await markFailed(row.id, e?.message || String(e));
      }
    }

    // ---- 5. ARC APPLICATIONS ----
    const buildiumClientId = Deno.env.get("BUILDIUM_API_KEY") ?? "";
    const buildiumClientSecret = Deno.env.get("BUILDIUM_API_SECRET") ?? "";

    for (const row of byKind.arc_application || []) {
      try {
        const p = { ...(row.payload || {}) };
        if (!p.unit_id && p._resolve_unit_buildium_id) {
          const u = unitByBuildium.get(String(p._resolve_unit_buildium_id));
          if (u) p.unit_id = u;
        }
        if (!p.owner_id && p._resolve_owner_buildium_id) {
          const o = ownerByBuildium.get(String(p._resolve_owner_buildium_id));
          if (o) p.owner_id = o.id;
        }
        // Fallback: pick any owner on the unit
        if (!p.owner_id && p.unit_id) {
          const { data: anyOwner } = await supabase.from("owners").select("id").eq("unit_id", p.unit_id).limit(1).maybeSingle();
          if (anyOwner) p.owner_id = anyOwner.id;
        }

        const files: Array<{ id: string; name: string }> = Array.isArray(p._arc_files) ? p._arc_files : [];
        const buildiumAssocId: string | null = p._arc_buildium_association_id || null;
        const buildiumArcId: string | null = p.buildium_arc_request_id || null;
        const decisionNotes: string | null = p.decision_notes || null;
        const reviewDate: string | null = p.review_date || null;
        const deciderName: string | null = p._arc_decider_name || null;
        const deciderDate: string | null = p._arc_decider_date || null;

        const clean = stripPrivate(p);

        let appId: string | null = null;
        if (row.action === "update" && row.match_id) {
          const { error } = await supabase.from("arc_applications").update(clean).eq("id", row.match_id);
          if (error) throw error;
          appId = row.match_id;
        } else {
          const { data: ins, error } = await supabase
            .from("arc_applications")
            .insert(clean)
            .select("id")
            .single();
          if (error) throw error;
          appId = ins.id;
        }

        // Seed a system comment with the Buildium decision (since comments/voters aren't exposed via API)
        if (appId && (decisionNotes || deciderName)) {
          const { data: existingComment } = await supabase
            .from("arc_application_comments")
            .select("id")
            .eq("application_id", appId)
            .is("user_id", null)
            .ilike("comment", "%[Imported from Buildium]%")
            .maybeSingle();
          if (!existingComment) {
            const seed =
              `[Imported from Buildium]\n` +
              (deciderName ? `Decision by: ${deciderName}${deciderDate ? ` on ${deciderDate}` : ""}\n` : "") +
              (reviewDate && !deciderDate ? `Decision date: ${reviewDate}\n` : "") +
              (decisionNotes ? `Decision notes: ${decisionNotes}` : "");
            await supabase.from("arc_application_comments").insert({
              application_id: appId,
              user_id: null,
              comment: seed.trim(),
            });
          }
        }

        // Download attached files from Buildium and upload into the arc-files bucket
        if (appId && files.length > 0 && buildiumClientId && buildiumClientSecret && buildiumAssocId && buildiumArcId) {
          for (const f of files) {
            try {
              if (!f.id) continue;
              // Buildium uses a presigned download endpoint
              // Buildium uses the global ownership-accounts ARC path; download endpoint is "downloadrequests" (plural) and POST
              const dlRes = await fetch(
                `${BUILDIUM_BASE}/v1/associations/ownershipaccounts/architecturalrequests/${buildiumArcId}/files/${f.id}/downloadrequests`,
                {
                  method: "POST",
                  headers: {
                    "x-buildium-client-id": buildiumClientId,
                    "x-buildium-client-secret": buildiumClientSecret,
                    Accept: "application/json",
                  },
                },
              );
              if (!dlRes.ok) {
                console.warn(`ARC file presign ${f.id} failed: ${dlRes.status}`);
                continue;
              }
              const dl: any = await dlRes.json().catch(() => ({}));
              const url: string | undefined = dl?.DownloadUrl || dl?.Url || dl?.url;
              if (!url) continue;
              const fileRes = await fetch(url);
              if (!fileRes.ok) continue;
              const buf = await fileRes.arrayBuffer();
              const safeName = (f.name || `buildium-${f.id}`).replace(/[^a-zA-Z0-9._-]/g, "_");
              const storagePath = `${clean.association_id}/${appId}/buildium-${f.id}-${safeName}`;
              await supabase.storage.from("arc-files").upload(storagePath, buf, {
                contentType: fileRes.headers.get("content-type") || "application/octet-stream",
                upsert: true,
              });
            } catch (e) {
              console.warn(`ARC file copy failed for ${f.id}: ${e}`);
            }
          }
        }

        await markApplied(row.id);
      } catch (e: any) {
        await markFailed(row.id, e?.message || String(e));
      }
    }

    return new Response(JSON.stringify({ success: true, applied, failed }), {
      status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  } catch (e: any) {
    console.error("buildium-import-apply error", e);
    return new Response(JSON.stringify({ error: e?.message || String(e) }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });
  }
});
