-- Keep one institution's national entities apart. Fixes 0031.
--
-- 0031 merged spellings by prefix — `citi%` -> 'Citibank'. Every merge it made
-- was provably one account (each split pair shared an account_number), but the
-- PREFIX generalises past that evidence: Citibank Australia and Citibank India
-- are different banks, different accounts, different currency, and `citi%`
-- flattens them into one. Older Citi India statements are queued to load, so
-- this would have merged them on arrival.
--
-- It is not a Citi problem. `hsbc%` and `%american express%` carry the identical
-- defect — all three are global banks operating as separate national entities.
--
-- WHY NOT DISCRIMINATE ON CURRENCY ALONE: Wise holds AUD, EUR and USD accounts
-- under one provider, so a blanket "non-AUD means a different bank" rule would
-- shatter it into three. Wise stays unmapped for exactly this reason — mapping
-- a genuinely multi-currency provider through this function would be wrong, and
-- that is now a documented property rather than an accident.
--
-- THE RULE: the country comes from the name when the name states it, and from
-- the currency otherwise. AUD is home and takes no suffix, so nothing renames
-- today — all 21 existing statements keep the name 0031 gave them.
--
--   'Citibank'          + AUD  -> 'Citibank'        (unchanged)
--   'Citi'              + AUD  -> 'Citibank'        (unchanged)
--   'Citibank Australia'+ AUD  -> 'Citibank'
--   'Citibank India'    + INR  -> 'Citibank India'
--   'Citibank N.A.'     + INR  -> 'Citibank India'  (currency carries it)
--   'HSBC'              + AUD  -> 'HSBC'            (unchanged)
--
-- Degradation is safe but not silent: an India statement whose currency was
-- extracted as AUD and whose name omits the country WILL merge. The tell is a
-- second account_number appearing under one bank_name with a different
-- currency — see the check in CLAUDE.md.
--
-- Idempotent: safe to re-run.

-- 'N.A.' is deliberately absent: it means "National Association", a US legal
-- form that appears on Citibank letterhead worldwide, including India. Reading
-- it as a country marker is precisely the mistake this migration exists to fix.
CREATE OR REPLACE FUNCTION bank_country_suffix(v TEXT, cur TEXT)
RETURNS TEXT AS $$
DECLARE
  c TEXT := upper(trim(coalesce(cur, '')));
BEGIN
  -- The name wins when it states a country: it is the source's own assertion,
  -- and it survives a currency that failed to extract.
  IF v LIKE '%australia%' OR v LIKE '% au%' THEN RETURN ''; END IF;  -- home
  IF v LIKE '%india%'                       THEN RETURN ' India'; END IF;
  IF v LIKE '%singapore%'                   THEN RETURN ' Singapore'; END IF;
  IF v LIKE '%hong kong%'                   THEN RETURN ' Hong Kong'; END IF;
  IF v LIKE '%new zealand%'                 THEN RETURN ' NZ'; END IF;
  IF v LIKE '%united kingdom%' OR v LIKE '% uk%' THEN RETURN ' UK'; END IF;
  IF v LIKE '%emirates%' OR v LIKE '% uae%' THEN RETURN ' UAE'; END IF;

  -- Otherwise infer from the statement's own currency. AUD is home.
  IF c = '' OR c = 'AUD' THEN RETURN ''; END IF;

  IF c = 'INR' THEN RETURN ' India'; END IF;
  IF c = 'SGD' THEN RETURN ' Singapore'; END IF;
  IF c = 'HKD' THEN RETURN ' Hong Kong'; END IF;
  IF c = 'NZD' THEN RETURN ' NZ'; END IF;
  IF c = 'GBP' THEN RETURN ' UK'; END IF;
  IF c = 'AED' THEN RETURN ' UAE'; END IF;
  IF c = 'USD' THEN RETURN ' US'; END IF;

  -- EUR spans countries and cannot name one, so fall back to the code itself.
  -- A distinguishable name beats a wrong one.
  RETURN ' ' || c;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- `currency` is a second parameter with a default so any existing single-arg
-- call keeps working; the trigger passes NEW.currency.
CREATE OR REPLACE FUNCTION normalize_bank_name(raw TEXT, cur TEXT DEFAULT NULL)
RETURNS TEXT AS $$
DECLARE
  v TEXT;
BEGIN
  IF raw IS NULL THEN
    RETURN NULL;
  END IF;

  v := lower(regexp_replace(trim(raw), '\s+', ' ', 'g'));

  IF v = '' THEN
    RETURN NULL;
  END IF;

  -- ---- Merges: institutions seen under more than one spelling ----
  -- Each carries a country suffix, empty for AUD, so one bank's national
  -- entities stay apart while its spellings collapse.

  IF v LIKE 'zip%' THEN
    RETURN 'Zip' || bank_country_suffix(v, cur);
  END IF;

  IF v LIKE '%american express%' OR v LIKE 'amex%' THEN
    RETURN 'American Express' || bank_country_suffix(v, cur);
  END IF;

  IF v LIKE 'citi%' THEN
    RETURN 'Citibank' || bank_country_suffix(v, cur);
  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' || bank_country_suffix(v, cur);
  END IF;

  IF v = 'anz' OR v LIKE 'anz %' OR v LIKE '%australia and new zealand banking%' THEN
    RETURN 'ANZ' || bank_country_suffix(v, cur);
  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' || bank_country_suffix(v, cur);
  END IF;

  IF v = 'ing' OR v LIKE 'ing %' THEN
    RETURN 'ING' || bank_country_suffix(v, cur);
  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.
  -- Wise reaches here, which is what keeps its AUD/EUR/USD accounts together.
  RETURN regexp_replace(trim(raw), '\s+', ' ', 'g');
END;
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE OR REPLACE FUNCTION statements_normalize_bank_name_trigger()
RETURNS TRIGGER AS $$
BEGIN
  NEW.bank_name := normalize_bank_name(NEW.bank_name, NEW.currency);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Re-point the trigger: it must fire when `currency` changes too, since the
-- country is now partly derived from it.
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, currency ON statements
  FOR EACH ROW EXECUTE FUNCTION statements_normalize_bank_name_trigger();

-- Backfill. Expected to touch nothing: every existing statement is AUD except
-- Wise, which is unmapped.
UPDATE statements
SET bank_name = normalize_bank_name(bank_name, currency)
WHERE bank_name IS DISTINCT FROM normalize_bank_name(bank_name, currency);
