#!/usr/bin/env python3
"""Regression: GET /api/fundraising/state heals blank grid-pill emails from the relational mirror.

The grid blob is canonical for the mobile "Edit investor" sheet, but an email can reach a linked
classic contact (email capture / a contact edit) without ever being written back into the blob pill
— so the edit form showed an empty email for a contact the directory clearly had (Grant, 2026-06-20).
The state handler now fills a blank pill email from fundraising_contacts.email, else the linked
contacts.email, matched by pill order then name. This asserts:
  - a blank pill whose linked contact has an email is HEALED on read;
  - a blank pill whose linked contact is also blank stays blank;
  - a pill that already carries an email in the blob is NEVER overwritten (fill-only).
Synthetic data only.

Run: cd backend && python3 test_grid_email_heal.py
"""
import http.client
import json
import os
import sqlite3
import sys
import tempfile
import threading
from http.server import ThreadingHTTPServer

_DATA = tempfile.mkdtemp()
os.environ["CRM_DATA_DIR"] = _DATA
os.environ["CRM_DB_PATH"] = os.path.join(_DATA, "crm.db")

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import server  # noqa: E402

FAILS = []


def check(cond, msg):
    print(("  PASS " if cond else "  FAIL ") + msg)
    if not cond:
        FAILS.append(msg)


class _Quiet(server.CRMHandler):
    def log_message(self, *a):
        pass


def _get_state(port, token):
    conn = http.client.HTTPConnection("127.0.0.1", port, timeout=10)
    conn.request("GET", "/api/fundraising/state", headers={"Authorization": "Bearer " + token})
    resp = conn.getresponse()
    raw = resp.read().decode("utf-8", "replace")
    conn.close()
    return resp.status, (json.loads(raw) if raw else None)


GRID = {
    "columns": [{"id": "investor_name", "label": "Investor", "type": "text"},
                {"id": "contacts", "label": "Contacts", "type": "contacts"}],
    "rows": [
        {"id": "rowW", "investor_name": "Wyoming", "notes": "",
         "contacts": [{"name": "Philip Treick", "email": "", "title": ""},
                      {"name": "Jose Briones", "email": "", "title": ""}]},
        {"id": "rowA", "investor_name": "Acme Capital", "notes": "",
         "contacts": [{"name": "Jane Doe", "email": "keep@acme.com", "title": ""}]},
        {"id": "rowO", "investor_name": "Orphan LP", "notes": "",
         "contacts": [{"name": "No Link", "email": "", "title": ""}]},
    ],
}


def seed():
    c = sqlite3.connect(os.environ["CRM_DB_PATH"])
    c.execute("INSERT INTO users (id,username,email,password_hash,full_name,role,is_active) "
              "VALUES ('u1','grant','grant@ten31.example','x','Grant','admin',1)")
    c.execute("INSERT INTO fundraising_state (id, grid_json, views_json, version) "
              "VALUES ('main', ?, '[]', 1) "
              "ON CONFLICT(id) DO UPDATE SET grid_json = excluded.grid_json", (json.dumps(GRID),))
    # Classic contacts directory: Jose has the captured email the blob never got; Philip is blank.
    c.execute("INSERT INTO contacts (id,first_name,last_name,email) VALUES "
              "('c-phil','Philip','Treick',''),"
              "('c-jose','Jose','Briones','jbriones@uwyo.edu'),"
              "('c-jane','Jane','Doe','other@acme.com')")  # differs from the blob's keep@acme.com
    # Relational mirror (what sync_fundraising_relational would build): blank fc.email, linked contact_id.
    c.execute("INSERT INTO fundraising_investors (id,investor_name,source_row_id,total_invested) VALUES "
              "('inv-w','Wyoming','rowW',0),('inv-a','Acme Capital','rowA',0),('inv-o','Orphan LP','rowO',0)")
    # fc-orphan has contact_id NULL (pre-0004 orphan) and blank email — nothing to heal from.
    c.execute("INSERT INTO fundraising_contacts (id,investor_id,full_name,email,sort_order,contact_id) VALUES "
              "('fc-phil','inv-w','Philip Treick','',0,'c-phil'),"
              "('fc-jose','inv-w','Jose Briones','',1,'c-jose'),"
              "('fc-jane','inv-a','Jane Doe','',0,'c-jane'),"
              "('fc-orphan','inv-o','No Link','',0,NULL)")
    c.commit()
    c.close()


def main():
    server.init_db()
    seed()
    token = server.create_token("u1", "grant", "admin")

    httpd = ThreadingHTTPServer(("127.0.0.1", 0), _Quiet)
    port = httpd.server_address[1]
    threading.Thread(target=httpd.serve_forever, daemon=True).start()
    try:
        st, d = _get_state(port, token)
        rows = ((d or {}).get("data", {}).get("grid", {}) or {}).get("rows", [])
        by_id = {r.get("id"): r for r in rows}
        w = by_id.get("rowW", {})
        a = by_id.get("rowA", {})
        wc = w.get("contacts", [])
        ac = a.get("contacts", [])

        print("\n[heal: blank pill email filled from the linked contact (Jose)]")
        jose = next((c for c in wc if c.get("name") == "Jose Briones"), {})
        check(st == 200 and jose.get("email") == "jbriones@uwyo.edu",
              f"Jose pill healed to jbriones@uwyo.edu (got {jose.get('email')!r})")

        print("\n[heal: blank pill whose contact is also blank stays blank (Philip)]")
        phil = next((c for c in wc if c.get("name") == "Philip Treick"), {})
        check(phil.get("email", "") == "",
              f"Philip pill stays blank (got {phil.get('email')!r})")

        print("\n[heal: a pill that already has an email is never overwritten (Jane)]")
        jane = next((c for c in ac if c.get("name") == "Jane Doe"), {})
        check(jane.get("email") == "keep@acme.com",
              f"Jane pill keeps its blob email, not the contact's (got {jane.get('email')!r})")

        print("\n[heal: a pill whose fundraising_contacts row has contact_id NULL stays blank (orphan)]")
        o = by_id.get("rowO", {})
        orphan = next((c for c in o.get("contacts", []) if c.get("name") == "No Link"), {})
        check(orphan.get("email", "") == "",
              f"orphan pill (no contact_id, no email source) stays blank (got {orphan.get('email')!r})")
    finally:
        httpd.shutdown()

    print()
    if FAILS:
        print(f"FAILED ({len(FAILS)}):")
        for f in FAILS:
            print(f"  - {f}")
        sys.exit(1)
    print("ALL PASS (grid email heal)")


if __name__ == "__main__":
    main()
