"""Backend tests for Invoice Share + Transaction Prefix Management features."""
import os
import pytest
import requests

BASE_URL = os.environ.get("REACT_APP_BACKEND_URL", "https://offline-billing-pro-2.preview.emergentagent.com").rstrip("/")
ADMIN_EMAIL = os.environ.get("TEST_ADMIN_EMAIL", "regalmarketing2024@gmail.com")
ADMIN_PASSWORD = os.environ.get("TEST_ADMIN_PASSWORD", "")
COMPANY_ID = os.environ.get("TEST_COMPANY_ID", "6a11b328f75fbff30e935034")
EXISTING_INVOICE_ID = os.environ.get("TEST_INVOICE_ID", "6a1af7ef367a0252e0545e15")
EXISTING_TOKEN = os.environ.get("TEST_SHARE_TOKEN", "")
EXISTING_LOCKED_PREFIX_ID = os.environ.get("TEST_LOCKED_PREFIX_ID", "6a252c91f923d85ce3974fa2")

if not ADMIN_PASSWORD:
    pytest.skip(
        "TEST_ADMIN_PASSWORD env var not set — populate /app/memory/test_credentials.md "
        "and export it before running these regression tests.",
        allow_module_level=True,
    )


@pytest.fixture(scope="module")
def session():
    s = requests.Session()
    r = s.post(f"{BASE_URL}/api/auth/login", json={"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD})
    assert r.status_code == 200, f"login failed: {r.status_code} {r.text}"
    return s


@pytest.fixture(scope="module")
def created_prefix_ids():
    return []


# ---------------- Invoice Share -----------------

class TestInvoiceShareSettings:
    def test_get_settings(self, session):
        r = session.get(f"{BASE_URL}/api/invoice-share/settings")
        assert r.status_code == 200
        d = r.json()
        for k in ["enable_share", "auto_download", "expiry_days", "watermark", "qr_verify", "wa_templates"]:
            assert k in d, f"missing key {k}"
        assert isinstance(d["wa_templates"], dict)

    def test_toggle_enable_share(self, session):
        # turn off
        r = session.put(f"{BASE_URL}/api/invoice-share/settings", json={"enable_share": False})
        assert r.status_code == 200
        assert r.json()["enable_share"] is False
        # turn back on
        r = session.put(f"{BASE_URL}/api/invoice-share/settings", json={"enable_share": True})
        assert r.status_code == 200
        assert r.json()["enable_share"] is True


class TestInvoiceShareToken:
    def test_issue_token(self, session):
        r = session.post(f"{BASE_URL}/api/invoice-share/token/{EXISTING_INVOICE_ID}")
        assert r.status_code == 200, r.text
        d = r.json()
        assert "token" in d
        assert "url" in d
        assert "/p/inv/" in d["url"]
        assert d["path"].startswith("/p/inv/")

    def test_public_view_no_auth(self):
        # Anonymous - no cookies
        r = requests.get(f"{BASE_URL}/api/invoice-share/public/{EXISTING_TOKEN}")
        assert r.status_code == 200, f"{r.status_code}: {r.text[:200]}"
        d = r.json()
        assert "invoice" in d
        assert "settings" in d
        assert "verification_hash" in d
        # _id stripped
        assert "_id" not in d["invoice"]

    def test_public_pdf_no_auth(self):
        r = requests.get(f"{BASE_URL}/api/invoice-share/public/{EXISTING_TOKEN}/pdf")
        assert r.status_code == 200
        assert r.headers.get("content-type", "").startswith("application/pdf")
        assert r.content[:4] == b"%PDF", "not a real PDF"
        assert len(r.content) > 1000

    def test_public_verify(self):
        r = requests.get(f"{BASE_URL}/api/invoice-share/public/{EXISTING_TOKEN}/verify")
        assert r.status_code == 200
        d = r.json()
        assert d.get("valid") is True
        assert "invoice_no" in d
        assert "total" in d
        assert "verification_hash" in d

    def test_invalid_token_404(self):
        # may be 404 or 405 since path has trailing slash; test no-slash variant
        r2 = requests.get(f"{BASE_URL}/api/invoice-share/public/INVALIDTOKEN12345")
        assert r2.status_code in (404, 410)


# ---------------- Transaction Prefixes -----------------

class TestPrefixCatalog:
    def test_catalog(self, session):
        r = session.get(f"{BASE_URL}/api/txn-prefixes/catalog")
        assert r.status_code == 200
        d = r.json()
        assert len(d["types"]) == 12
        assert len(d["tokens"]) == 4
        keys = {t["key"] for t in d["types"]}
        for k in ["sale", "purchase", "quotation", "credit_note", "debit_note"]:
            assert k in keys


class TestPrefixCRUD:
    def test_create_prefix(self, session, created_prefix_ids):
        payload = {
            "company_id": COMPANY_ID,
            "type": "quotation",
            "name": "TEST_Quotation_Series",
            "template": "TST/{yy}/{seq}",
            "starting_number": 100,
            "padding": 4,
        }
        r = session.post(f"{BASE_URL}/api/txn-prefixes", json=payload)
        assert r.status_code == 200, r.text
        d = r.json()
        assert d["template"] == "TST/{yy}/{seq}"
        assert d["current_number"] == 0
        # first prefix for type => is_default true
        assert d.get("is_default") in (True, False)  # may already exist
        created_prefix_ids.append(d["id"])

    def test_next_number_atomic(self, session, created_prefix_ids):
        assert created_prefix_ids, "must have a created prefix"
        pid = created_prefix_ids[0]
        # First call
        r = session.post(f"{BASE_URL}/api/txn-prefixes/next-number", json={"prefix_id": pid})
        assert r.status_code == 200
        first = r.json()["invoice_no"]
        # Second call should increment
        r = session.post(f"{BASE_URL}/api/txn-prefixes/next-number", json={"prefix_id": pid})
        assert r.status_code == 200
        second = r.json()["invoice_no"]
        assert first != second
        # numeric part should increase
        def _num(s): 
            import re
            m = re.findall(r"\d+", s)
            return int(m[-1])
        assert _num(second) == _num(first) + 1

    def test_reset_series(self, session, created_prefix_ids):
        pid = created_prefix_ids[0]
        r = session.post(f"{BASE_URL}/api/txn-prefixes/{pid}/reset-series", json={"next_number": 5000})
        assert r.status_code == 200
        d = r.json()
        assert d["current_number"] == 4999
        # Generate next - should be 5000
        r = session.post(f"{BASE_URL}/api/txn-prefixes/next-number", json={"prefix_id": pid})
        assert r.status_code == 200
        inv = r.json()["invoice_no"]
        assert "5000" in inv

    def test_lock_blocks_edit_delete(self, session, created_prefix_ids):
        pid = created_prefix_ids[0]
        # Lock
        r = session.post(f"{BASE_URL}/api/txn-prefixes/{pid}/lock")
        assert r.status_code == 200
        assert r.json().get("is_locked") is True
        # Edit should fail with 423
        r = session.put(f"{BASE_URL}/api/txn-prefixes/{pid}", json={"name": "X"})
        assert r.status_code == 423
        # Delete should fail with 423
        r = session.delete(f"{BASE_URL}/api/txn-prefixes/{pid}")
        assert r.status_code == 423
        # Unlock
        r = session.post(f"{BASE_URL}/api/txn-prefixes/{pid}/unlock")
        assert r.status_code == 200
        assert r.json().get("is_locked") is False

    def test_delete_used_archives(self, session, created_prefix_ids):
        pid = created_prefix_ids[0]
        # current_number > 0 because we generated numbers
        r = session.delete(f"{BASE_URL}/api/txn-prefixes/{pid}")
        assert r.status_code == 200
        d = r.json()
        assert d["deleted"] is False
        assert d["archived"] is True

    def test_delete_unused_hard_deletes(self, session):
        # Create a fresh prefix without using it
        payload = {
            "company_id": COMPANY_ID,
            "type": "challan",
            "name": "TEST_Challan_Unused",
            "template": "TCH/{yy}/{seq}",
            "starting_number": 1,
            "padding": 3,
        }
        r = session.post(f"{BASE_URL}/api/txn-prefixes", json=payload)
        assert r.status_code == 200
        pid = r.json()["id"]
        assert r.json()["current_number"] == 0
        # delete unused
        r = session.delete(f"{BASE_URL}/api/txn-prefixes/{pid}")
        assert r.status_code == 200
        d = r.json()
        assert d["deleted"] is True
        assert d["archived"] is False

    def test_audit_log(self, session, created_prefix_ids):
        pid = created_prefix_ids[0]
        r = session.get(f"{BASE_URL}/api/txn-prefixes/{pid}/audit")
        assert r.status_code == 200
        audits = r.json()
        assert isinstance(audits, list)
        actions = {a["action"] for a in audits}
        # We should see at least create, reset_series, lock, unlock, archive
        for need in ["create", "reset_series", "lock", "unlock"]:
            assert need in actions, f"missing audit action: {need} (got {actions})"
        # user_email and ip should be captured
        sample = audits[0]
        assert "user_email" in sample
        assert "ip" in sample
        assert sample["user_email"] == ADMIN_EMAIL


class TestPrefixCleanup:
    def test_cleanup_test_prefixes(self, session):
        """Best-effort cleanup of TEST_ prefixed prefixes (must unlock first)."""
        r = session.get(f"{BASE_URL}/api/txn-prefixes?company_id={COMPANY_ID}")
        if r.status_code != 200:
            return
        for p in r.json():
            if p.get("name", "").startswith("TEST_"):
                if p.get("is_locked"):
                    session.post(f"{BASE_URL}/api/txn-prefixes/{p['id']}/unlock")
                session.delete(f"{BASE_URL}/api/txn-prefixes/{p['id']}")
