"""Checks for the deterministic invoice result v1 boundary."""

from __future__ import annotations

import json
import sys
import unittest
from pathlib import Path
from unittest.mock import patch

from jsonschema import validate


PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
loaded_platform = sys.modules.get("platform")
if loaded_platform is not None and not hasattr(loaded_platform, "__path__"):
    del sys.modules["platform"]

from apps.wp_invoices.services.canonical_result import build_canonical_result, _normalize_tax_summary
from apps.wp_invoices.services.extractor import MODEL_MAP
from apps.wp_invoices.services.pipeline import extract_canonical_invoice


APP_ROOT = Path(__file__).resolve().parents[1]
SCHEMA = json.loads(
    (APP_ROOT / "contracts" / "invoice_result.v1.schema.json").read_text(encoding="utf-8")
)


EXTRACTED = {
    "invoice_type": {"verbatim": "Corporate", "confidence": 0.9},
    "header": {
        "supplier": {
            "name": {"verbatim": "Example Supplier", "confidence": 0.95},
            "abn": {"verbatim": "51 824 753 556", "confidence": 0.9},
        },
        "invoice": {
            "number": {"verbatim": "INV-42", "confidence": 0.92},
            "date": {
                "issue_date": {"verbatim": "2026-07-23", "confidence": 0.91},
            },
        },
    },
    "items": [
        {
            "description": {"verbatim": "Service", "confidence": 0.93},
            "qty": {"verbatim": "2", "confidence": 0.9},
            "unit_price": {"verbatim": "$50.00", "confidence": 0.9},
            "gst_line": {"computed": "10.00", "confidence": 0.8},
            "line_total": {"verbatim": "100.00", "confidence": 0.94},
        }
    ],
    "totals": {
        "subtotal": {"verbatim": "100.00", "confidence": 0.95},
        "gst": {"verbatim": "10.00", "confidence": 0.95},
        "grand_total": {"verbatim": "AUD 110.00", "confidence": 0.96},
    },
    "steps": "Private model reasoning must not cross the public boundary.",
    "server_path": "/private/invoice.pdf",
}


class CanonicalResultChecks(unittest.TestCase):
    def test_product_code_is_optional_and_derived_from_leading_source_token(self):
        from apps.wp_invoices.services.canonical_result import _normalize_item_v2
        coded = _normalize_item_v2({"description": {"verbatim": "Tuna", "computed": None, "confidence": .9},
                                    "source_text": "59496 Tuna Flav 95g 1.09 A", "source_line": 3})
        uncoded = _normalize_item_v2({"description": {"verbatim": "Service", "computed": None, "confidence": .9},
                                      "source_text": "Service fee 1.00", "source_line": 4})
        self.assertEqual("59496", coded["product_code"])
        self.assertEqual("observed_unverified", coded["product_code_evidence"]["evidence_state"])
        self.assertIsNone(uncoded["product_code"])
    def test_tax_rates_are_decimal_and_total_comes_only_from_rows(self):
        summary = _normalize_tax_summary({
            "tax_inclusive": True,
            "tax_total": {"verbatim": "99.99", "computed": 99.99, "confidence": 1},
            "lines": [
                {"tax_code": "A", "rate": 0, "net_amount": 68.12, "tax_amount": 0},
                {"tax_code": "B", "rate": 10, "net_amount": 19.80, "tax_amount": 1.98},
            ],
        }, 99.99)
        self.assertEqual([0.0, 0.1], [line["rate"] for line in summary["lines"]])
        self.assertEqual(1.98, summary["tax_total"])
        self.assertEqual("derived_from_tax_summary_rows", summary["tax_total_origin"])

    def test_mini_profile_uses_available_vision_model(self):
        self.assertEqual("gpt-4o-mini", MODEL_MAP["mini"])

    def _result(self, extracted=None, filename="invoice.pdf"):
        return build_canonical_result(
            extracted=EXTRACTED if extracted is None else extracted,
            invoice_id="inv-1",
            filename=filename,
            content_type="application/pdf",
            size_bytes=123,
            artifact_id="artifact-1",
            engine="mini",
        )

    def test_result_matches_canonical_json_schema(self):
        result = self._result()
        validate(instance=result, schema=SCHEMA)
        self.assertEqual("completed", result["status"])
        self.assertEqual(110.0, result["total"])
        self.assertTrue(result["validations"]["mathematically_consistent"])

    def test_public_result_omits_reasoning_evidence_and_internal_paths(self):
        result = self._result(filename="../../private/invoice.pdf")
        serialized = json.dumps(result)
        self.assertEqual("invoice.pdf", result["document"]["filename"])
        self.assertNotIn("steps", serialized)
        self.assertNotIn("server_path", serialized)
        self.assertNotIn("/private/", serialized)

    def test_computed_value_takes_precedence_and_currency_defaults_to_aud(self):
        extracted = json.loads(json.dumps(EXTRACTED))
        extracted["totals"]["grand_total"]["computed"] = "111.00"
        result = self._result(extracted)
        self.assertEqual(111.0, result["total"])
        self.assertEqual("AUD", result["currency"])
        self.assertFalse(result["validations"]["subtotal_plus_tax_matches_total"])
        self.assertIn("subtotal_plus_tax_does_not_match_total", result["warnings"])

    def test_missing_fields_are_null_with_stable_warning_codes(self):
        result = self._result({})
        validate(instance=result, schema=SCHEMA)
        self.assertIsNone(result["supplier"]["name"])
        self.assertIsNone(result["total"])
        self.assertEqual(
            [
                "supplier_name_missing",
                "invoice_number_missing",
                "invoice_date_missing",
                "line_items_missing",
                "total_missing",
            ],
            result["warnings"],
        )

    def test_math_is_recomputed_instead_of_trusting_model_flags(self):
        extracted = json.loads(json.dumps(EXTRACTED))
        extracted["totals"]["validations"] = {
            "items_sum_matches_subtotal": False,
            "subtotal_plus_gst_plus_extras_equals_grand_total": False,
        }
        result = self._result(extracted)
        self.assertTrue(result["validations"]["items_sum_matches_subtotal"])
        self.assertTrue(result["validations"]["subtotal_plus_tax_matches_total"])

    def test_pipeline_boundary_preserves_context_for_ai_usage_accounting(self):
        context = {"tenant": {"tenant_id": "tenant-a"}, "request_id": "request-1"}
        with patch(
            "apps.wp_invoices.services.pipeline.process_invoice_bytes",
            return_value={"extracted": EXTRACTED, "checks": {}},
        ) as process:
            result = extract_canonical_invoice(
                file_bytes=b"document",
                filename="invoice.pdf",
                content_type="application/pdf",
                invoice_id="inv-1",
                artifact_id="artifact-1",
                context=context,
            )
        process.assert_called_once_with(
            file_bytes=b"document",
            filename="invoice.pdf",
            content_type="application/pdf",
            engine="mini",
            context=context,
        )
        self.assertEqual(8, result["document"]["size_bytes"])


if __name__ == "__main__":
    unittest.main(verbosity=2)
