"""Trash/Restore expansion + Print Customization smoke + regression tests.

Covers the new diff:
- Parties trash/restore/purge
- Items trash/restore/purge (data round-trip integrity)
- Expenses trash/restore/purge
- Invoices trash/restore/purge (regression)
- /api/trash/summary
- /api/trash/empty (kind-specific + all)
- Regression smoke: invoices, units, reminders/overdue, GST filing gstr1, OCR auth-required
"""
import uuid
import requests

from conftest import BASE_URL


# ---------- Helpers ----------
def _mk_party(s, company_id, name=None):
    name = name or f"TEST_TrashParty_{uuid.uuid4().hex[:6]}"
    r = s.post(f"{BASE_URL}/api/parties?company_id={company_id}",
               json={"name": name, "type": "customer"}, timeout=30)
    assert r.status_code == 200, r.text
    return r.json()


def _mk_item(s, company_id, payload=None):
    p = payload or {
        "name": f"TEST_TrashItem_{uuid.uuid4().hex[:6]}",
        "code": f"CODE-{uuid.uuid4().hex[:4]}",
        "hsn": "8504",
        "gst_rate": 12.0,
        "sale_price": 250.0,
        "purchase_price": 200.0,
        "opening_stock": 33,
        "current_stock": 33,
        "low_stock_threshold": 5,
        "unit": "PCS",
    }
    r = s.post(f"{BASE_URL}/api/items?company_id={company_id}", json=p, timeout=30)
    assert r.status_code == 200, r.text
    return r.json()


def _mk_expense(s, company_id, amount=1234.0):
    r = s.post(f"{BASE_URL}/api/expenses?company_id={company_id}",
               json={"category": f"TEST_TrashExp_{uuid.uuid4().hex[:4]}",
                     "amount": amount, "vendor": "TEST_TrashVendor"}, timeout=30)
    assert r.status_code == 200, r.text
    return r.json()


# ======================== PARTIES TRASH ========================
class TestPartiesTrash:
    def test_party_delete_restore_purge_full_flow(self, admin_session, default_company_id):
        cid = default_company_id
        p = _mk_party(admin_session, cid)
        pid = p["id"]
        original_name = p["name"]

        # Soft delete
        r = admin_session.delete(f"{BASE_URL}/api/parties/{pid}", timeout=30)
        assert r.status_code == 200

        # Not in active list
        r2 = admin_session.get(f"{BASE_URL}/api/parties?company_id={cid}", timeout=30)
        assert not any(x["id"] == pid for x in r2.json())

        # In trash
        r3 = admin_session.get(f"{BASE_URL}/api/parties-trash?company_id={cid}", timeout=30)
        assert r3.status_code == 200
        trashed = [x for x in r3.json() if x["id"] == pid]
        assert trashed and trashed[0]["name"] == original_name
        assert "deleted_at" in trashed[0]

        # Restore
        r4 = admin_session.post(f"{BASE_URL}/api/parties/{pid}/restore", timeout=30)
        assert r4.status_code == 200
        assert r4.json()["name"] == original_name

        # Back in active list
        r5 = admin_session.get(f"{BASE_URL}/api/parties?company_id={cid}", timeout=30)
        assert any(x["id"] == pid for x in r5.json())

        # Delete again and permanent purge
        admin_session.delete(f"{BASE_URL}/api/parties/{pid}", timeout=30)
        rp = admin_session.delete(f"{BASE_URL}/api/parties-trash/{pid}", timeout=30)
        assert rp.status_code == 200

        # Not in trash, not in active
        r6 = admin_session.get(f"{BASE_URL}/api/parties-trash?company_id={cid}", timeout=30)
        assert not any(x["id"] == pid for x in r6.json())

    def test_restore_party_not_in_trash_returns_404(self, admin_session):
        bad_id = "0" * 24
        r = admin_session.post(f"{BASE_URL}/api/parties/{bad_id}/restore", timeout=30)
        assert r.status_code == 404


# ======================== ITEMS TRASH (data integrity) ========================
class TestItemsTrash:
    def test_item_delete_restore_roundtrip_preserves_data(self, admin_session, default_company_id):
        cid = default_company_id
        unique = uuid.uuid4().hex[:6]
        original = {
            "name": f"TEST_RTItem_{unique}",
            "code": f"RT-{unique}",
            "hsn": "8504",
            "gst_rate": 12.0,
            "sale_price": 250.0,
            "purchase_price": 200.0,
            "opening_stock": 33,
            "current_stock": 33,
            "low_stock_threshold": 5,
            "unit": "PCS",
        }
        item = _mk_item(admin_session, cid, original)
        iid = item["id"]

        # Soft delete
        r = admin_session.delete(f"{BASE_URL}/api/items/{iid}", timeout=30)
        assert r.status_code == 200

        # In trash
        r2 = admin_session.get(f"{BASE_URL}/api/items-trash?company_id={cid}", timeout=30)
        assert r2.status_code == 200
        trashed = [x for x in r2.json() if x["id"] == iid]
        assert trashed, "item missing from trash"

        # Not in active
        r3 = admin_session.get(f"{BASE_URL}/api/items?company_id={cid}", timeout=30)
        assert not any(x["id"] == iid for x in r3.json())

        # Restore
        r4 = admin_session.post(f"{BASE_URL}/api/items/{iid}/restore", timeout=30)
        assert r4.status_code == 200

        # Verify ALL critical fields round-trip
        r5 = admin_session.get(f"{BASE_URL}/api/items?company_id={cid}", timeout=30)
        restored = next((x for x in r5.json() if x["id"] == iid), None)
        assert restored is not None, "restored item missing from active list"
        assert restored["name"] == original["name"]
        assert restored.get("code") == original["code"]
        assert restored["hsn"] == original["hsn"]
        assert restored["gst_rate"] == original["gst_rate"]
        assert float(restored["current_stock"]) == float(original["current_stock"])
        assert restored.get("unit") == original["unit"]

        # Purge
        admin_session.delete(f"{BASE_URL}/api/items/{iid}", timeout=30)
        rp = admin_session.delete(f"{BASE_URL}/api/items-trash/{iid}", timeout=30)
        assert rp.status_code == 200

    def test_purge_non_existent_item_404(self, admin_session):
        r = admin_session.delete(f"{BASE_URL}/api/items-trash/{'0'*24}", timeout=30)
        assert r.status_code == 404


# ======================== EXPENSES TRASH ========================
class TestExpensesTrash:
    def test_expense_full_trash_cycle(self, admin_session, default_company_id):
        cid = default_company_id
        e = _mk_expense(admin_session, cid, amount=999.0)
        eid = e["id"]

        # Soft delete
        r = admin_session.delete(f"{BASE_URL}/api/expenses/{eid}", timeout=30)
        assert r.status_code == 200

        # Not in active
        r2 = admin_session.get(f"{BASE_URL}/api/expenses?company_id={cid}", timeout=30)
        assert not any(x["id"] == eid for x in r2.json())

        # In trash
        r3 = admin_session.get(f"{BASE_URL}/api/expenses-trash?company_id={cid}", timeout=30)
        assert r3.status_code == 200
        assert any(x["id"] == eid for x in r3.json())

        # Restore
        r4 = admin_session.post(f"{BASE_URL}/api/expenses/{eid}/restore", timeout=30)
        assert r4.status_code == 200

        # Back in active
        r5 = admin_session.get(f"{BASE_URL}/api/expenses?company_id={cid}", timeout=30)
        active = next((x for x in r5.json() if x["id"] == eid), None)
        assert active is not None
        assert float(active["amount"]) == 999.0

        # Purge
        admin_session.delete(f"{BASE_URL}/api/expenses/{eid}", timeout=30)
        rp = admin_session.delete(f"{BASE_URL}/api/expenses-trash/{eid}", timeout=30)
        assert rp.status_code == 200


# ======================== INVOICES TRASH PURGE (regression + new purge) ========================
class TestInvoicesTrashPurge:
    def test_invoice_purge_endpoint(self, admin_session, default_company_id):
        cid = default_company_id
        # Create + delete a simple invoice
        item = _mk_item(admin_session, cid)
        payload = {"type": "sale", "party_name": "TEST_PurgeInv",
                   "lines": [{"item_id": item["id"], "name": item["name"], "qty": 1,
                              "rate": 100.0, "gst_rate": 12.0}]}
        ri = admin_session.post(f"{BASE_URL}/api/invoices?company_id={cid}", json=payload, timeout=30)
        assert ri.status_code == 200, ri.text
        iid = ri.json()["id"]

        # Soft delete -> trash
        admin_session.delete(f"{BASE_URL}/api/invoices/{iid}", timeout=30)
        # Purge
        rp = admin_session.delete(f"{BASE_URL}/api/invoices-trash/{iid}", timeout=30)
        assert rp.status_code == 200
        # No longer in trash
        r = admin_session.get(f"{BASE_URL}/api/invoices-trash?company_id={cid}", timeout=30)
        assert not any(x["id"] == iid for x in r.json())


# ======================== TRASH SUMMARY ========================
class TestTrashSummary:
    def test_summary_shape_and_counts_increase_on_delete(self, admin_session, default_company_id):
        cid = default_company_id

        r0 = admin_session.get(f"{BASE_URL}/api/trash/summary?company_id={cid}", timeout=30)
        assert r0.status_code == 200
        base = r0.json()
        for key in ["invoices", "parties", "items", "expenses"]:
            assert key in base
            assert isinstance(base[key], int)

        # Delete one party + one item + one expense
        p = _mk_party(admin_session, cid)
        i = _mk_item(admin_session, cid)
        e = _mk_expense(admin_session, cid)
        admin_session.delete(f"{BASE_URL}/api/parties/{p['id']}", timeout=30)
        admin_session.delete(f"{BASE_URL}/api/items/{i['id']}", timeout=30)
        admin_session.delete(f"{BASE_URL}/api/expenses/{e['id']}", timeout=30)

        r1 = admin_session.get(f"{BASE_URL}/api/trash/summary?company_id={cid}", timeout=30)
        cur = r1.json()
        assert cur["parties"] >= base["parties"] + 1
        assert cur["items"] >= base["items"] + 1
        assert cur["expenses"] >= base["expenses"] + 1

        # Cleanup via empty kind=
        for kind, eid in [("parties", p["id"]), ("items", i["id"]), ("expenses", e["id"])]:
            admin_session.delete(f"{BASE_URL}/api/{kind}-trash/{eid}", timeout=30)


# ======================== TRASH EMPTY ========================
class TestTrashEmpty:
    def test_empty_specific_kind_items(self, admin_session, default_company_id):
        cid = default_company_id
        # Create two items and delete them
        ids = []
        for _ in range(2):
            x = _mk_item(admin_session, cid)
            admin_session.delete(f"{BASE_URL}/api/items/{x['id']}", timeout=30)
            ids.append(x["id"])

        # Confirm they're in trash
        r = admin_session.get(f"{BASE_URL}/api/items-trash?company_id={cid}", timeout=30)
        in_trash_ids = {x["id"] for x in r.json()}
        assert all(i in in_trash_ids for i in ids)

        # Empty items
        r2 = admin_session.post(f"{BASE_URL}/api/trash/empty?company_id={cid}&kind=items", timeout=30)
        assert r2.status_code == 200, r2.text
        d = r2.json()["deleted"]
        assert "items" in d and d["items"] >= 2

        # Items trash now empty for this company
        r3 = admin_session.get(f"{BASE_URL}/api/items-trash?company_id={cid}", timeout=30)
        assert r3.json() == []

    def test_empty_all_kinds_no_kind_param(self, admin_session, default_company_id):
        cid = default_company_id
        # Seed one of each
        p = _mk_party(admin_session, cid)
        i = _mk_item(admin_session, cid)
        e = _mk_expense(admin_session, cid)
        admin_session.delete(f"{BASE_URL}/api/parties/{p['id']}", timeout=30)
        admin_session.delete(f"{BASE_URL}/api/items/{i['id']}", timeout=30)
        admin_session.delete(f"{BASE_URL}/api/expenses/{e['id']}", timeout=30)

        r = admin_session.post(f"{BASE_URL}/api/trash/empty?company_id={cid}", timeout=30)
        assert r.status_code == 200, r.text
        d = r.json()["deleted"]
        # All 4 collections returned
        for k in ["invoices", "parties", "items", "expenses"]:
            assert k in d

        # Summary should be 0 across the board (for this company)
        r2 = admin_session.get(f"{BASE_URL}/api/trash/summary?company_id={cid}", timeout=30)
        s = r2.json()
        assert s["parties"] == 0
        assert s["items"] == 0
        assert s["expenses"] == 0
        assert s["invoices"] == 0


# ======================== REGRESSION SMOKE ========================
class TestRegressionSmoke:
    def test_units_endpoints(self, admin_session, default_company_id):
        cid = default_company_id
        # Create
        unique = f"TST_{uuid.uuid4().hex[:4]}"
        r = admin_session.post(
            f"{BASE_URL}/api/units?company_id={cid}",
            json={"code": unique, "name": f"Test Unit {unique}"}, timeout=30)
        # 200/201 either accepted, but must NOT be 5xx
        assert r.status_code < 500, r.text
        # List
        r2 = admin_session.get(f"{BASE_URL}/api/units?company_id={cid}", timeout=30)
        assert r2.status_code == 200

    def test_reminders_overdue(self, admin_session, default_company_id):
        r = admin_session.get(
            f"{BASE_URL}/api/reminders/overdue?company_id={default_company_id}", timeout=30)
        assert r.status_code == 200
        assert isinstance(r.json(), (list, dict))

    def test_gst_filing_gstr1(self, admin_session, default_company_id):
        r = admin_session.get(
            f"{BASE_URL}/api/gst-filing/gstr1?company_id={default_company_id}", timeout=30)
        # Should respond without 5xx
        assert r.status_code < 500, r.text

    def test_ocr_scan_bill_requires_auth(self):
        s = requests.Session()
        # No auth -> must reject (401/403)
        r = s.post(f"{BASE_URL}/api/ocr/scan-bill", timeout=30)
        assert r.status_code in (401, 403, 422), f"unexpected {r.status_code}: {r.text[:200]}"

    def test_invoices_list_still_works(self, admin_session, default_company_id):
        r = admin_session.get(f"{BASE_URL}/api/invoices?company_id={default_company_id}", timeout=30)
        assert r.status_code == 200
        assert isinstance(r.json(), list)
