"""Vyapar parity invoice features test suite (iteration 19).

Covers:
- PartyIn extended fields: email, shipping_address, opening_balance,
  opening_balance_type ('debit'/'credit'), credit_limit.
- InvoiceIn `prefix_id` field — default series when null, specific series when set.
- list_invoices `q` search by invoice_no / party_name.
"""
from __future__ import annotations

import time
import uuid
import pytest
import requests

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


def _tag() -> str:
    return f"VYP-{int(time.time() * 1000)}-{uuid.uuid4().hex[:6]}"


# ---------------------------------------------------------------------------
# Party extended fields
# ---------------------------------------------------------------------------
class TestPartyExtendedFields:
    def test_create_party_with_full_vyapar_payload(self, admin_session, default_company_id):
        tag = _tag()
        payload = {
            "name": f"TEST_Party_{tag}",
            "phone": "9123456780",
            "type": "customer",
            "email": f"test_{tag}@example.com",
            "gstin": "27ABCDE1234F1Z5",
            "address": "12 Billing Street, Mumbai",
            "shipping_address": "99 Shipping Lane, Pune",
            "state": "Maharashtra",
            "opening_balance": 5000,
            "opening_balance_type": "debit",
            "credit_limit": 25000,
        }
        r = admin_session.post(
            f"{BASE_URL}/api/parties?company_id={default_company_id}",
            json=payload,
            timeout=20,
        )
        assert r.status_code in (200, 201), f"Create failed: {r.status_code} {r.text}"
        body = r.json()
        pid = body.get("id") or body.get("_id")
        assert pid, f"No id: {body}"

        # Verify persistence via GET list
        rg = admin_session.get(
            f"{BASE_URL}/api/parties",
            params={"company_id": default_company_id, "q": f"TEST_Party_{tag}"},
            timeout=15,
        )
        assert rg.status_code == 200, rg.text
        parties = rg.json()
        if isinstance(parties, dict):
            parties = parties.get("items") or parties.get("data") or []
        match = next((p for p in parties if p.get("id") == pid), None)
        assert match, f"Party {pid} not in list"

        # Vyapar parity field assertions
        assert match.get("email") == payload["email"], f"email mismatch: {match.get('email')}"
        assert match.get("shipping_address") == payload["shipping_address"], (
            f"shipping_address mismatch: {match.get('shipping_address')}"
        )
        assert float(match.get("opening_balance") or 0) == 5000.0, (
            f"opening_balance: {match.get('opening_balance')}"
        )
        assert match.get("opening_balance_type") == "debit", (
            f"opening_balance_type: {match.get('opening_balance_type')}"
        )
        assert float(match.get("credit_limit") or 0) == 25000.0, (
            f"credit_limit: {match.get('credit_limit')}"
        )

    def test_create_party_credit_balance_type(self, admin_session, default_company_id):
        tag = _tag()
        payload = {
            "name": f"TEST_Cred_{tag}",
            "type": "vendor",
            "opening_balance": 1500,
            "opening_balance_type": "credit",
        }
        r = admin_session.post(
            f"{BASE_URL}/api/parties?company_id={default_company_id}",
            json=payload, timeout=15,
        )
        assert r.status_code in (200, 201), r.text
        pid = r.json().get("id")
        assert pid
        rg = admin_session.get(
            f"{BASE_URL}/api/parties",
            params={"company_id": default_company_id, "q": f"TEST_Cred_{tag}"},
            timeout=15,
        )
        parties = rg.json()
        if isinstance(parties, dict):
            parties = parties.get("items") or []
        match = next((p for p in parties if p.get("id") == pid), None)
        assert match
        assert match.get("opening_balance_type") == "credit", match


# ---------------------------------------------------------------------------
# Invoice prefix_id + q search
# ---------------------------------------------------------------------------
class TestInvoicePrefixAndSearch:
    @pytest.fixture(scope="class")
    def party_id(self, admin_session, default_company_id):
        tag = _tag()
        r = admin_session.post(
            f"{BASE_URL}/api/parties?company_id={default_company_id}",
            json={"name": f"TEST_InvParty_{tag}", "type": "customer", "phone": "9000000001"},
            timeout=15,
        )
        assert r.status_code in (200, 201), r.text
        return r.json().get("id")

    @pytest.fixture(scope="class")
    def item_id(self, admin_session, default_company_id):
        tag = _tag()
        r = admin_session.post(
            f"{BASE_URL}/api/items?company_id={default_company_id}",
            json={"name": f"TEST_InvItem_{tag}", "sale_price": 100, "gst_rate": 18, "unit": "pcs"},
            timeout=15,
        )
        assert r.status_code in (200, 201), r.text
        return r.json().get("id")

    def test_invoice_with_default_prefix(self, admin_session, default_company_id, party_id, item_id):
        """prefix_id null/omitted ⇒ default series number assigned."""
        payload = {
            "party_id": party_id,
            "type": "sale",
            "lines": [{"item_id": item_id, "name": "Line A", "qty": 1, "rate": 100, "gst_rate": 18, "unit": "pcs"}],
        }
        r = admin_session.post(
            f"{BASE_URL}/api/invoices?company_id={default_company_id}",
            json=payload, timeout=20,
        )
        assert r.status_code in (200, 201), r.text
        inv = r.json()
        inv_no = inv.get("invoice_no") or inv.get("invoiceNo")
        assert inv_no, f"No invoice_no: {inv}"
        # Should look like INV/...  or have / separator
        assert "/" in str(inv_no) or str(inv_no).isdigit(), (
            f"Unexpected invoice_no shape: {inv_no}"
        )

    def test_invoice_with_explicit_prefix_id(self, admin_session, default_company_id, party_id, item_id):
        """Pass prefix_id = first available series id. Verify returned invoice_no uses that series template."""
        # Fetch series list
        rs = admin_session.get(
            f"{BASE_URL}/api/txn-prefixes",
            params={"company_id": default_company_id, "type": "sale"},
            timeout=15,
        )
        if rs.status_code != 200:
            pytest.skip(f"txn-prefixes endpoint not available: {rs.status_code} {rs.text[:120]}")
        series_list = rs.json()
        if isinstance(series_list, dict):
            series_list = series_list.get("items") or series_list.get("data") or []
        if not series_list:
            pytest.skip("No prefix series configured for sale invoices")
        chosen = series_list[0]
        chosen_id = chosen.get("id") or chosen.get("_id")
        chosen_prefix = chosen.get("prefix") or chosen.get("template") or ""
        assert chosen_id, f"No id in series: {chosen}"

        payload = {
            "party_id": party_id,
            "type": "sale",
            "prefix_id": chosen_id,
            "lines": [{"item_id": item_id, "name": "Line B", "qty": 1, "rate": 200, "gst_rate": 18, "unit": "pcs"}],
        }
        r = admin_session.post(
            f"{BASE_URL}/api/invoices?company_id={default_company_id}",
            json=payload, timeout=20,
        )
        assert r.status_code in (200, 201), r.text
        inv = r.json()
        inv_no = inv.get("invoice_no") or ""
        assert inv_no, f"No invoice_no in response: {inv}"
        # If we know the series prefix, the invoice_no should start with it
        if chosen_prefix:
            stripped = chosen_prefix.replace("{YY}", "").replace("{YYYY}", "").replace("{n}", "").split("{")[0].rstrip("/")
            if stripped:
                assert stripped in inv_no, (
                    f"Invoice_no {inv_no!r} does not contain expected prefix fragment {stripped!r} "
                    f"(series template was {chosen_prefix!r})"
                )

    def test_invoice_list_q_search_by_invoice_no(self, admin_session, default_company_id, party_id, item_id):
        # Create an invoice with a known fragment
        payload = {
            "party_id": party_id, "type": "sale",
            "lines": [{"item_id": item_id, "name": "Searchable", "qty": 1, "rate": 50, "gst_rate": 18, "unit": "pcs"}],
        }
        r = admin_session.post(
            f"{BASE_URL}/api/invoices?company_id={default_company_id}", json=payload, timeout=20,
        )
        assert r.status_code in (200, 201), r.text
        inv = r.json()
        inv_no = inv.get("invoice_no") or ""
        assert inv_no
        # Search by leading fragment (e.g., "INV")
        frag = "".join(c for c in inv_no if c.isalpha())[:3] or inv_no[:3]
        rs = admin_session.get(
            f"{BASE_URL}/api/invoices",
            params={"company_id": default_company_id, "type": "sale", "q": frag, "limit": 50},
            timeout=15,
        )
        assert rs.status_code == 200, rs.text
        items = rs.json()
        if isinstance(items, dict):
            items = items.get("items") or items.get("data") or []
        # At least our invoice should appear
        assert any(i.get("id") == inv.get("id") for i in items), (
            f"Created invoice {inv.get('id')} not returned by q={frag!r}"
        )

    def test_invoice_list_q_search_by_party_name(self, admin_session, default_company_id, party_id, item_id):
        # Get party name
        rp = admin_session.get(
            f"{BASE_URL}/api/parties",
            params={"company_id": default_company_id, "limit": 200}, timeout=15,
        )
        assert rp.status_code == 200
        parties = rp.json()
        if isinstance(parties, dict):
            parties = parties.get("items") or []
        party = next((p for p in parties if p.get("id") == party_id), None)
        assert party, f"Party {party_id} not found in list"
        pname = party.get("name") or ""
        # Create an invoice with explicit party_name (frontend always sends this)
        payload = {
            "party_id": party_id, "party_name": pname, "type": "sale",
            "lines": [{"item_id": item_id, "name": "Searchable2", "qty": 1, "rate": 75, "gst_rate": 18, "unit": "pcs"}],
        }
        rc = admin_session.post(
            f"{BASE_URL}/api/invoices?company_id={default_company_id}", json=payload, timeout=20,
        )
        assert rc.status_code in (200, 201), rc.text
        created_inv_id = rc.json().get("id")

        frag = pname[:10] if pname else "TEST_InvParty"
        rs = admin_session.get(
            f"{BASE_URL}/api/invoices",
            params={"company_id": default_company_id, "type": "sale", "q": frag, "limit": 50},
            timeout=15,
        )
        assert rs.status_code == 200, rs.text
        items = rs.json()
        if isinstance(items, dict):
            items = items.get("items") or []
        assert any(i.get("id") == created_inv_id for i in items), (
            f"q={frag!r} did not return invoice {created_inv_id} for party_name={pname!r}"
        )
