-- Normalise statements.bank_name so one institution is one name.
--
-- WHY THIS IS NOT COSMETIC: `uq_statement_identity` is
-- (bank_name, account_number, billing_end_date). The bank name is a *component
-- of the duplicate check*, so two spellings of one institution mean the same
-- statement can be ingested twice without the index ever firing. Gemini reads
-- the name off whatever the PDF happens to print, so the spelling varies per
-- document, not per account.
--
-- Zip made this visible. One account (2705256) arrived under four names in
-- sixteen minutes — 'ZipMoney Payments Pty Ltd', 'ZipMoney Payments Pty
-- Limited', 'Zip Pay', 'ZipPay' — with ten or more still in the ingestion
-- queue. It was not unique: 17 bank_name values represented 13 institutions,
-- and every split pair shared an identical account number.
--
--   American Express (14) / AMERICAN EXPRESS (1)  -> XXXX-XXXXXX-01000
--   Citibank (8)          / Citi (4)              -> 5258073330340253
--   National Australia Bank Limited (3) / ... (NAB) (1) -> 1015001000116227
--
-- Fragmentation also splits the statements-page bank filter, the transactions
-- bank filter, and by_bank in /api/analytics/fees.
--
-- THE VOCABULARY IS OPEN, unlike statement_type in 0013. An unrecognised name
-- passes through tidied, never collapsed to a fallback: a bank this household
-- has never used must be able to arrive without a migration, and destroying its
-- name on first contact is far worse than leaving it unmapped. That is also why
-- there is deliberately NO CHECK constraint here.
--
-- Aliases for banks that only ever arrive one way (Westpac, ANZ, HSBC, ING,
-- AMP, Up, CommBank) are no-ops today and map TO the spelling already in use,
-- so they cannot rename anything. They exist to catch the legal-entity variant
-- the next PDF might print — which is exactly how Zip got here.
--
-- Wise is deliberately left alone: 'Wise Australia Pty Ltd.' is a single
-- spelling, so shortening it would be a rename nobody asked for rather than a
-- merge. Add a branch if a second spelling ever shows up.
--
-- Idempotent: safe to re-run.

CREATE OR REPLACE FUNCTION normalize_bank_name(raw TEXT)
RETURNS TEXT AS $$
DECLARE
  v TEXT;
BEGIN
  IF raw IS NULL THEN
    RETURN NULL;
  END IF;

  -- Collapse whitespace before matching so 'Zip  Pay' and 'Zip Pay' agree.
  v := lower(regexp_replace(trim(raw), '\s+', ' ', 'g'));

  IF v = '' THEN
    -- An empty string is not a name. NULL is how the schema already spells
    -- "unknown": bankLabel() renders it 'Manual' and uq_statement_identity
    -- excludes it.
    RETURN NULL;
  END IF;

  -- ---- Merges: institutions seen under more than one spelling ----

  -- 'Zip Pay', 'ZipPay', 'ZipMoney Payments Pty Ltd', 'ZipMoney Payments Pty
  -- Limited'. Anchored so a merchant that merely contains "zip" cannot match.
  IF v LIKE 'zip%' THEN
    RETURN 'Zip';
  END IF;

  IF v LIKE '%american express%' OR v LIKE 'amex%' THEN
    RETURN 'American Express';
  END IF;

  -- Citi, Citibank, Citigroup — one card, 5258073330340253.
  IF v LIKE 'citi%' THEN
    RETURN 'Citibank';
  END IF;

  IF v LIKE '%national australia bank%' OR v = 'nab' THEN
    RETURN 'NAB';
  END IF;

  -- ---- Defensive: map future legal-entity variants onto the name in use ----

  IF v LIKE '%westpac%' THEN
    RETURN 'Westpac';
  END IF;

  IF v = 'anz' OR v LIKE 'anz %' OR v LIKE '%australia and new zealand banking%' THEN
    RETURN 'ANZ';
  END IF;

  IF v LIKE '%commonwealth bank%' OR v LIKE 'commbank%' OR v = 'cba' THEN
    RETURN 'Commonwealth Bank';
  END IF;

  IF v LIKE 'hsbc%' THEN
    RETURN 'HSBC';
  END IF;

  IF v = 'ing' OR v LIKE 'ing %' THEN
    RETURN 'ING';
  END IF;

  IF v = 'amp' OR v LIKE 'amp %' THEN
    RETURN 'AMP Bank';
  END IF;

  IF v = 'up' OR v LIKE 'up bank%' THEN
    RETURN 'Up';
  END IF;

  -- Unknown institution: keep exactly what was extracted, tidied only.
  RETURN regexp_replace(trim(raw), '\s+', ' ', 'g');
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- Normalise on write so the N8N workflow keeps sending whatever the PDF prints.
CREATE OR REPLACE FUNCTION statements_normalize_bank_name_trigger()
RETURNS TRIGGER AS $$
BEGIN
  NEW.bank_name := normalize_bank_name(NEW.bank_name);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_statements_normalize_bank_name ON statements;
CREATE TRIGGER trg_statements_normalize_bank_name
  BEFORE INSERT OR UPDATE OF bank_name ON statements
  FOR EACH ROW EXECUTE FUNCTION statements_normalize_bank_name_trigger();

-- Backfill. Verified collision-free against uq_statement_identity before
-- writing this: no two statements share (normalised name, account_number,
-- billing_end_date).
UPDATE statements
SET bank_name = normalize_bank_name(bank_name)
WHERE bank_name IS DISTINCT FROM normalize_bank_name(bank_name);

-- account_owner_mappings keys on (bank_name, account_number) with its own
-- UNIQUE constraint, so a rename on `statements` would strand its rows. The
-- table is empty today; this keeps it aligned if it is ever populated before
-- this migration runs somewhere else. ON CONFLICT because two spellings of one
-- bank could otherwise merge into one key.
UPDATE account_owner_mappings
SET bank_name = normalize_bank_name(bank_name)
WHERE bank_name IS DISTINCT FROM normalize_bank_name(bank_name)
  AND NOT EXISTS (
    SELECT 1 FROM account_owner_mappings other
    WHERE other.bank_name = normalize_bank_name(account_owner_mappings.bank_name)
      AND other.account_number = account_owner_mappings.account_number
  );
