"""Smoke tests for Auto + Manual unified bill numbering + multi-user counters.

User spec (v12.7):
  - Auto + Manual bills share ONE continuous sequence per (company, user, FY, type)
  - User A and User B have INDEPENDENT counters (no collision)
  - Different companies have INDEPENDENT counters
  - Manual entry MUST bump the counter forward
  - No duplicate bill numbers WITHIN a user's own sequence
  - No skipped numbers

These tests hit the LIVE backend (uvicorn @ BASE_URL) using `requests` —
matching the project's `test_smoke.py` pattern.
"""
import time

import pytest
import requests

from conftest import BASE_URL, ADMIN_EMAIL, ADMIN_PASSWORD  # type: ignore[import-not-found]


def _login(email: str = ADMIN_EMAIL, password: str = ADMIN_PASSWORD) -> requests.Session:
    s = requests.Session()
    s.headers.update({"Content-Type": "application/json"})
    r = s.post(f"{BASE_URL}/api/auth/login", json={"email": email, "password": password}, timeout=15)
    assert r.status_code == 200, f"login failed for {email}: {r.text}"
    return s


def _create_test_user(admin: requests.Session, email: str, password: str = "TestPass#123", role: str = "manager") -> bool:
    """Ensure a secondary user exists. Returns True if created, False if already there."""
    r = admin.post(
        f"{BASE_URL}/api/users",
        json={"email": email, "password": password, "name": email.split("@")[0], "role": role, "is_active": True},
        timeout=15,
    )
    # 200 created, 400 likely 'email exists'
    return r.status_code == 200


def _seed_prefix(admin: requests.Session, company_id: str, prefix_str: str = "RM/2026-27/") -> str:
    """Create (or fetch) a default Sale prefix with literal string + reset counter to 0.
    Also wipes any per-user counter docs for the prefix so tests start fresh."""
    # Look for an existing default for sale on this company
    r = admin.get(f"{BASE_URL}/api/txn-prefixes", params={"company_id": company_id, "type": "sale"}, timeout=15)
    assert r.status_code == 200, r.text
    rows = r.json()
    matching = [p for p in rows if p.get("template", "").startswith(prefix_str) or p.get("template") == prefix_str + "{seq}"]
    if matching:
        pid = matching[0]["id"]
        # Reset to 0 so seq starts at 1
        rr = admin.post(f"{BASE_URL}/api/txn-prefixes/{pid}/reset-series", json={"next_number": 1}, timeout=15)
        assert rr.status_code == 200, rr.text
        # Wipe per-user counters via the helper admin endpoint
        admin.delete(f"{BASE_URL}/api/txn-prefixes/{pid}/user-counters", timeout=15)
        return pid

    # Create fresh
    body = {
        "company_id": company_id,
        "type": "sale",
        "name": f"Auto Test Sale Series {int(time.time())}",
        "template": f"{prefix_str}{{seq}}",
        "fy": "2026-27",
        "starting_number": 1,
        "current_number": 0,
        "padding": 1,                # no zero padding so we get plain numbers (1, 2, 3…)
        "is_default": True,
    }
    r = admin.post(f"{BASE_URL}/api/txn-prefixes", json=body, timeout=15)
    assert r.status_code == 200, r.text
    pid = r.json()["id"]
    admin.delete(f"{BASE_URL}/api/txn-prefixes/{pid}/user-counters", timeout=15)
    return pid


def _post_invoice(sess: requests.Session, company_id: str, prefix_id: str, invoice_no_override: str = "") -> dict:
    body = {
        "type": "sale", "party_id": None, "party_name": "Test Cust", "party_phone": "",
        "party_state": "", "party_gstin": "", "billing_address": "", "shipping_address": "",
        "invoice_date": "2026-06-15", "due_date": "2026-06-15",
        "lines": [{"item_id": None, "name": "Test Item", "hsn": "", "qty": 1, "unit": "PCS",
                   "rate": 100, "gst_rate": 0, "discount_pct": 0, "amount": 100}],
        "notes": "", "payment_mode": "cash", "payment_received": 100,
        "prefix_id": prefix_id,
        "invoice_no_override": invoice_no_override,
    }
    r = sess.post(f"{BASE_URL}/api/invoices", json=body, params={"company_id": company_id}, timeout=20)
    assert r.status_code == 200, f"POST /invoices failed: {r.status_code} {r.text}"
    return r.json()


@pytest.mark.critical
class TestUnifiedSequence:
    def test_auto_bills_increment_continuously(self, admin_session, default_company_id):
        prefix_id = _seed_prefix(admin_session, default_company_id, "TUSC/2026-27/")
        first = _post_invoice(admin_session, default_company_id, prefix_id)
        second = _post_invoice(admin_session, default_company_id, prefix_id)
        third = _post_invoice(admin_session, default_company_id, prefix_id)
        nums = [first["invoice_no"], second["invoice_no"], third["invoice_no"]]
        # Trailing integer must be 1, 2, 3 — strictly increasing by 1
        import re
        seqs = [int(re.search(r"(\d+)$", n).group(1)) for n in nums]
        assert seqs == sorted(seqs), f"sequences out of order: {seqs}"
        # Must all be different and contiguous
        assert seqs[1] - seqs[0] == 1, seqs
        assert seqs[2] - seqs[1] == 1, seqs

    def test_manual_bumps_counter_forward(self, admin_session, default_company_id):
        unique = f"TUSM{int(time.time()*1000)}/2026-27/"
        prefix_id = _seed_prefix(admin_session, default_company_id, unique)
        # Auto bill #1
        _post_invoice(admin_session, default_company_id, prefix_id)
        # Manual bill = 5 (user jumped ahead)
        b2 = _post_invoice(admin_session, default_company_id, prefix_id, invoice_no_override=f"{unique}5")
        assert b2["invoice_no"].endswith("5"), b2["invoice_no"]
        # Next AUTO bill must be 6, NOT 2 — counter was bumped by manual
        b3 = _post_invoice(admin_session, default_company_id, prefix_id)
        import re
        next_seq = int(re.search(r"(\d+)$", b3["invoice_no"]).group(1))
        assert next_seq == 6, f"expected next auto = 6 after manual=5, got {next_seq} (inv_no={b3['invoice_no']})"

    def test_manual_smaller_than_current_rejected_as_duplicate(self, admin_session, default_company_id):
        """If user types a manual number that's already used in their own sequence,
        we MUST reject (no overwrite)."""
        prefix_id = _seed_prefix(admin_session, default_company_id, "TUSR/2026-27/")
        b1 = _post_invoice(admin_session, default_company_id, prefix_id)  # → /1
        _post_invoice(admin_session, default_company_id, prefix_id)  # → /2
        # Attempt to re-issue /1 manually
        body = {
            "type": "sale", "party_id": None, "party_name": "X", "party_phone": "",
            "party_state": "", "party_gstin": "", "billing_address": "", "shipping_address": "",
            "invoice_date": "2026-06-15", "due_date": "2026-06-15",
            "lines": [{"item_id": None, "name": "Y", "hsn": "", "qty": 1, "unit": "PCS",
                       "rate": 50, "gst_rate": 0, "discount_pct": 0, "amount": 50}],
            "notes": "", "payment_mode": "cash", "payment_received": 50,
            "prefix_id": prefix_id, "invoice_no_override": b1["invoice_no"],
        }
        r = admin_session.post(f"{BASE_URL}/api/invoices", json=body, params={"company_id": default_company_id}, timeout=15)
        assert r.status_code == 400, r.text
        assert "already exists" in r.text.lower() or "duplicate" in r.text.lower()

    def test_year_change_resets_counter(self, admin_session, default_company_id):
        """A prefix with fy=2026-27 has its own counter; a prefix with fy=2027-28
        on the same (company, user, type) starts fresh from 1."""
        p2627 = _seed_prefix(admin_session, default_company_id, "TUYY/2026-27/")
        _post_invoice(admin_session, default_company_id, p2627)
        _post_invoice(admin_session, default_company_id, p2627)
        # New FY prefix series
        body = {
            "company_id": default_company_id, "type": "sale",
            "name": f"YearChange {int(time.time())}",
            "template": "TUYY/2027-28/{seq}",
            "fy": "2027-28", "starting_number": 1, "current_number": 0,
            "padding": 1, "is_default": False,
        }
        r = admin_session.post(f"{BASE_URL}/api/txn-prefixes", json=body, timeout=15)
        assert r.status_code == 200, r.text
        p2728 = r.json()["id"]
        b_new = _post_invoice(admin_session, default_company_id, p2728)
        import re
        new_seq = int(re.search(r"(\d+)$", b_new["invoice_no"]).group(1))
        assert new_seq == 1, f"new FY should start at 1, got {new_seq} ({b_new['invoice_no']})"
        # Old FY counter unchanged — issue another and confirm it's 3
        b_old3 = _post_invoice(admin_session, default_company_id, p2627)
        old_seq = int(re.search(r"(\d+)$", b_old3["invoice_no"]).group(1))
        assert old_seq == 3, f"old FY counter should continue from 2 → 3, got {old_seq}"


@pytest.mark.critical
class TestMultiUserCounters:
    """Two users on the same company + prefix MUST have independent counters."""

    def test_two_users_have_independent_sequences(self, admin_session, default_company_id):
        # Create a secondary user
        peer_email = f"peer{int(time.time())}@example.com"
        peer_pw = "PeerPass#456"
        _create_test_user(admin_session, peer_email, peer_pw, role="manager")
        peer = _login(peer_email, peer_pw)

        prefix_id = _seed_prefix(admin_session, default_company_id, "TUMU/2026-27/")

        # Admin issues 3 bills → /1, /2, /3 (under admin's counter)
        a1 = _post_invoice(admin_session, default_company_id, prefix_id)
        a2 = _post_invoice(admin_session, default_company_id, prefix_id)
        a3 = _post_invoice(admin_session, default_company_id, prefix_id)

        # Peer issues 2 bills → /1, /2 (under peer's INDEPENDENT counter)
        p1 = _post_invoice(peer, default_company_id, prefix_id)
        p2 = _post_invoice(peer, default_company_id, prefix_id)

        import re
        admin_seqs = [int(re.search(r"(\d+)$", x["invoice_no"]).group(1)) for x in [a1, a2, a3]]
        peer_seqs = [int(re.search(r"(\d+)$", x["invoice_no"]).group(1)) for x in [p1, p2]]
        assert admin_seqs == [1, 2, 3], admin_seqs
        assert peer_seqs == [1, 2], peer_seqs

        # Admin's NEXT bill is /4, NOT influenced by peer's /1 /2
        a4 = _post_invoice(admin_session, default_company_id, prefix_id)
        admin_4 = int(re.search(r"(\d+)$", a4["invoice_no"]).group(1))
        assert admin_4 == 4, admin_4

    def test_user_manual_bumps_only_own_counter(self, admin_session, default_company_id):
        """Manual entry by User A must not move User B's counter."""
        peer_email = f"peerb{int(time.time())}@example.com"
        peer_pw = "PeerPass#789"
        _create_test_user(admin_session, peer_email, peer_pw, role="manager")
        peer = _login(peer_email, peer_pw)

        unique = f"TUMM{int(time.time()*1000)}/2026-27/"
        prefix_id = _seed_prefix(admin_session, default_company_id, unique)
        _post_invoice(admin_session, default_company_id, prefix_id)  # admin /1
        # Admin manual jumps to /50
        _post_invoice(admin_session, default_company_id, prefix_id, invoice_no_override=f"{unique}50")
        # Peer's first bill must still be /1 (untouched by admin's jump)
        peer_b1 = _post_invoice(peer, default_company_id, prefix_id)
        import re
        peer_seq = int(re.search(r"(\d+)$", peer_b1["invoice_no"]).group(1))
        assert peer_seq == 1, f"peer's counter polluted by admin manual: got {peer_seq}"
