-- Make bill creation truly two-way between the app (public.bills / bill_approvals)
-- and the Accounting platform (accounting.bills), and mirror pending bills.
--
--   1. bill_should_mirror now includes 'pending' so approval bills appear in
--      Payables immediately (still excludes draft/rejected/void/cancelled/denied).
--   2. A bill created NATIVELY in the Accounting module (external_source IS NULL)
--      now creates a matching public.bills row. The accounting row is pre-linked
--      to the new public id so the existing forward sync adopts it (no duplicate),
--      the vendor is mapped back to public.vendors, and the line's GL is carried.
--   3. Backfill: mirror existing pending public bills + reverse-create eligible
--      native accounting bills.

-- ---------------------------------------------------------------------------
-- 1) Mirror pending bills too.
-- ---------------------------------------------------------------------------
create or replace function accounting.bill_should_mirror(_status text)
returns boolean language sql immutable as $$
  select coalesce(lower(_status), '') not in
    ('draft','rejected','void','voided','cancelled','denied');
$$;

-- ---------------------------------------------------------------------------
-- 2) Reverse creation: native accounting bill -> public.bills (idempotent-ish;
--    only acts on unlinked native rows).
-- ---------------------------------------------------------------------------
create or replace function accounting.create_public_from_accounting_bill(_acct_id uuid)
returns uuid
language plpgsql security definer set search_path to 'public','accounting'
as $$
declare
  ab accounting.bills%rowtype;
  _assoc uuid;
  _pub_vendor uuid;
  _avs text; _avext text; _avname text;
  _exp uuid;
  _new_id uuid;
  _pub_status text;
begin
  select * into ab from accounting.bills where id=_acct_id;
  if not found then return null; end if;

  -- only native, real, non-void bills
  if ab.external_source is not null then return null; end if;
  if coalesce(ab.auto_created,false) then return null; end if;
  if ab.source_payment_id is not null then return null; end if;
  if ab.status::text = 'void' then return null; end if;

  select association_id into _assoc from accounting.companies where id=ab.company_id;
  if _assoc is null then return null; end if;

  -- Map the vendor back to a single public.vendors roster (find-or-create).
  _pub_vendor := null;
  if ab.vendor_id is not null then
    select external_source, external_id, name into _avs, _avext, _avname
      from accounting.vendors where id=ab.vendor_id;
    if _avs='acmacc_vendor' and nullif(_avext,'') is not null then
      _pub_vendor := _avext::uuid;
    end if;
    if _pub_vendor is null or not exists (select 1 from public.vendors where id=_pub_vendor) then
      select id into _pub_vendor from public.vendors
        where (association_id=_assoc or _assoc = any(association_ids))
          and lower(trim(name))=lower(trim(coalesce(_avname,''))) limit 1;
      if _pub_vendor is null then
        insert into public.vendors (name, association_id, is_active)
        values (coalesce(nullif(_avname,''),'Vendor'), _assoc, true)
        returning id into _pub_vendor;
      end if;
      update accounting.vendors
         set external_source='acmacc_vendor', external_id=_pub_vendor::text, updated_at=now()
       where id=ab.vendor_id and nullif(external_id,'') is null;
    end if;
  end if;

  -- Carry the first line's GL account if it is a valid public COA id.
  select account_id into _exp from accounting.bill_items
    where bill_id=ab.id and account_id is not null limit 1;
  if _exp is not null and not exists (select 1 from public.chart_of_accounts where id=_exp) then
    _exp := null;
  end if;

  _pub_status := case ab.status::text when 'paid' then 'paid' else 'approved' end;
  _new_id := gen_random_uuid();

  -- Pre-link the accounting row so the forward sync (fired by the insert below)
  -- updates THIS row on conflict instead of creating a duplicate mirror.
  update accounting.bills
     set external_source='acmacc_bill', external_id=_new_id::text, updated_at=now()
   where id=ab.id;

  insert into public.bills
    (id, association_id, vendor_id, invoice_number, bill_date, due_date,
     amount, amount_paid, expense_account_id, description, status, attachment_url, approved_date)
  values
    (_new_id, _assoc, _pub_vendor, ab.number, coalesce(ab.issue_date, current_date), ab.due_date,
     coalesce(ab.total,0), coalesce(ab.paid_amount,0), _exp, ab.notes,
     _pub_status, ab.attachment_url,
     case when _pub_status in ('approved','paid') then current_date else null end);

  return _new_id;
end;
$$;

create or replace function accounting.tg_accounting_bill_reverse_create()
returns trigger
language plpgsql security definer set search_path to 'public','accounting'
as $$
begin
  begin
    if new.external_source is null
       and not coalesce(new.auto_created,false)
       and new.source_payment_id is null
       and new.status::text <> 'void' then
      perform accounting.create_public_from_accounting_bill(new.id);
    end if;
  exception when others then
    raise warning 'accounting: reverse bill create failed for %: %', new.id, sqlerrm;
  end;
  return new;
end;
$$;

drop trigger if exists trg_acct_bill_reverse_create on accounting.bills;
create trigger trg_acct_bill_reverse_create
  after insert on accounting.bills
  for each row execute function accounting.tg_accounting_bill_reverse_create();

-- ---------------------------------------------------------------------------
-- 3a) Backfill: mirror existing pending public bills into Payables now.
-- ---------------------------------------------------------------------------
do $$
declare r record;
begin
  for r in select id from public.bills where lower(coalesce(status,'')) = 'pending' loop
    perform accounting.sync_public_bill(r.id);
  end loop;
end $$;

-- ---------------------------------------------------------------------------
-- 3b) Backfill: reverse-create eligible native accounting bills.
-- ---------------------------------------------------------------------------
do $$
declare r record;
begin
  for r in
    select id from accounting.bills
    where external_source is null
      and not coalesce(auto_created,false)
      and source_payment_id is null
      and status::text <> 'void'
  loop
    perform accounting.create_public_from_accounting_bill(r.id);
  end loop;
end $$;
