-- Sync the main-app owner ledger (public.owner_ledger_entries) into the
-- Accounting platform per association:
--   * a CHARGE (debit > 0)  -> accounting.invoices  (the customer's AR ledger)
--   * a PAYMENT (credit > 0) -> accounting.payments_received, deposited=false
--     (Undeposited Funds) so the funds flow into the "Make Deposit" page.
-- The accounting customer's balance is recomputed authoritatively from the
-- source ledger. Scoped to associations that have an accounting.companies row.
-- Keyed by external_source='acmacc_ledger', external_id=owner_ledger_entries.id.

-- payments_received needs external linkage columns (invoices/bills/vendors already have them)
alter table accounting.payments_received add column if not exists external_source text;
alter table accounting.payments_received add column if not exists external_id text;
create unique index if not exists ux_payments_received_ext
  on accounting.payments_received (company_id, external_source, external_id)
  where external_id is not null;

-- ---------------------------------------------------------------------------
-- Recompute a synced customer's balance from the source ledger and FIFO-apply
-- ledger payments across that customer's ledger invoices (so paid_amount/status
-- look right). Only touches acmacc_ledger rows — manual invoices/payments are
-- left alone.
-- ---------------------------------------------------------------------------
create or replace function accounting.recompute_customer_from_ledger(
  _company_id uuid, _customer_id uuid, _unit_id uuid, _association_id uuid
) returns void
language plpgsql security definer set search_path to 'public','accounting'
as $$
declare
  _bal  numeric;
  _pool numeric;
  _alloc numeric;
  inv   record;
begin
  if _customer_id is null or _unit_id is null then return; end if;

  -- Authoritative AR balance = sum(debit - credit) of the unit's live ledger.
  select coalesce(sum(debit - credit), 0) into _bal
  from public.owner_ledger_entries
  where unit_id = _unit_id and association_id = _association_id
    and coalesce(is_archived, false) = false;
  update accounting.customers set balance = _bal where id = _customer_id;

  -- FIFO-allocate ledger payments across ledger invoices.
  select coalesce(sum(amount), 0) into _pool
  from accounting.payments_received
  where customer_id = _customer_id and external_source = 'acmacc_ledger';

  for inv in
    select id, total from accounting.invoices
    where customer_id = _customer_id and external_source = 'acmacc_ledger'
    order by issue_date asc, created_at asc
  loop
    _alloc := greatest(0, least(_pool, inv.total));
    update accounting.invoices set
      paid_amount = _alloc,
      status = case when _alloc >= inv.total and inv.total > 0
                    then 'paid'::accounting.invoice_status
                    else 'sent'::accounting.invoice_status end,
      updated_at = now()
    where id = inv.id;
    _pool := _pool - _alloc;
  end loop;
end;
$$;

-- ---------------------------------------------------------------------------
-- Sync one ledger entry into accounting (idempotent).
-- ---------------------------------------------------------------------------
create or replace function accounting.sync_owner_ledger_entry(_entry_id uuid)
returns void
language plpgsql security definer set search_path to 'public','accounting'
as $$
declare
  e public.owner_ledger_entries%rowtype;
  _company_id uuid;
  _customer_id uuid;
begin
  select * into e from public.owner_ledger_entries where id = _entry_id;
  if not found then return; end if;

  select id into _company_id from accounting.companies where association_id = e.association_id;
  if _company_id is null then return; end if;

  select id into _customer_id from accounting.customers
  where company_id = _company_id and external_source = 'acmacc_unit'
    and external_id = e.unit_id::text;
  if _customer_id is null then return; end if; -- unit not synced; nothing to attach to

  if coalesce(e.is_archived, false) then
    delete from accounting.invoices          where company_id=_company_id and external_source='acmacc_ledger' and external_id=e.id::text;
    delete from accounting.payments_received where company_id=_company_id and external_source='acmacc_ledger' and external_id=e.id::text;

  elsif coalesce(e.debit, 0) > 0 then
    -- charge -> invoice (drop any stale payment row for this entry)
    delete from accounting.payments_received where company_id=_company_id and external_source='acmacc_ledger' and external_id=e.id::text;
    insert into accounting.invoices
      (company_id, customer_id, number, issue_date, due_date, status, subtotal, tax, total, notes, external_source, external_id)
    values
      (_company_id, _customer_id,
       'AR-' || to_char(e.date, 'YYYYMMDD') || '-' || left(replace(e.id::text, '-', ''), 6),
       e.date, e.date, 'sent'::accounting.invoice_status,
       e.debit, 0, e.debit,
       coalesce(nullif(e.description, ''), initcap(e.transaction_type)),
       'acmacc_ledger', e.id::text)
    on conflict (company_id, external_source, external_id) where external_id is not null
    do update set customer_id=excluded.customer_id, issue_date=excluded.issue_date,
       due_date=excluded.due_date, subtotal=excluded.subtotal, total=excluded.total,
       notes=excluded.notes, number=excluded.number, updated_at=now();

  elsif coalesce(e.credit, 0) > 0 then
    -- payment -> payments_received (Undeposited Funds); never reset deposited/deposit_id
    delete from accounting.invoices where company_id=_company_id and external_source='acmacc_ledger' and external_id=e.id::text;
    insert into accounting.payments_received
      (company_id, customer_id, payment_date, amount, method, reference, memo, deposited, external_source, external_id)
    values
      (_company_id, _customer_id, e.date, e.credit,
       coalesce(nullif(e.transaction_type, ''), 'payment'),
       e.reference_id, e.description, false, 'acmacc_ledger', e.id::text)
    on conflict (company_id, external_source, external_id) where external_id is not null
    do update set customer_id=excluded.customer_id, payment_date=excluded.payment_date,
       amount=excluded.amount, method=excluded.method, reference=excluded.reference,
       memo=excluded.memo, updated_at=now();

  else
    delete from accounting.invoices          where company_id=_company_id and external_source='acmacc_ledger' and external_id=e.id::text;
    delete from accounting.payments_received where company_id=_company_id and external_source='acmacc_ledger' and external_id=e.id::text;
  end if;

  perform accounting.recompute_customer_from_ledger(_company_id, _customer_id, e.unit_id, e.association_id);
end;
$$;

-- Remove synced rows for a deleted/moved ledger entry, then recompute.
create or replace function accounting.unsync_owner_ledger_entry(
  _entry_id uuid, _association_id uuid, _unit_id uuid
) returns void
language plpgsql security definer set search_path to 'public','accounting'
as $$
declare _company_id uuid; _customer_id uuid;
begin
  select id into _company_id from accounting.companies where association_id=_association_id;
  if _company_id is null then return; end if;
  delete from accounting.invoices          where company_id=_company_id and external_source='acmacc_ledger' and external_id=_entry_id::text;
  delete from accounting.payments_received where company_id=_company_id and external_source='acmacc_ledger' and external_id=_entry_id::text;
  select id into _customer_id from accounting.customers
    where company_id=_company_id and external_source='acmacc_unit' and external_id=_unit_id::text;
  perform accounting.recompute_customer_from_ledger(_company_id, _customer_id, _unit_id, _association_id);
end;
$$;

-- Trigger glue (error-swallowed so a sync hiccup never blocks a ledger write).
create or replace function accounting.tg_owner_ledger_sync()
returns trigger
language plpgsql security definer set search_path to 'public','accounting'
as $$
begin
  begin
    if tg_op = 'DELETE' then
      perform accounting.unsync_owner_ledger_entry(old.id, old.association_id, old.unit_id);
      return old;
    end if;
    if tg_op = 'UPDATE'
       and (old.unit_id is distinct from new.unit_id
            or old.association_id is distinct from new.association_id) then
      perform accounting.unsync_owner_ledger_entry(old.id, old.association_id, old.unit_id);
    end if;
    perform accounting.sync_owner_ledger_entry(new.id);
    return new;
  exception when others then
    raise warning 'accounting: owner_ledger sync failed for %: %', coalesce(new.id, old.id), sqlerrm;
    return coalesce(new, old);
  end;
end;
$$;

drop trigger if exists trg_acct_sync_owner_ledger on public.owner_ledger_entries;
create trigger trg_acct_sync_owner_ledger
  after insert or update or delete on public.owner_ledger_entries
  for each row execute function accounting.tg_owner_ledger_sync();
