from __future__ import annotations

import copy
import unittest

from arr_ingestion.contracts import DeliveryEnvelope, IngestionError


SHA_A = "a" * 64
SHA_B = "b" * 64
SHA_C = "c" * 64
SHA_D = "d" * 64
SHA_E = "e" * 64


def artifact(
    object_key: str,
    original_filename: str,
    sha256: str,
    byte_size: int,
    mime_type: str,
) -> dict[str, object]:
    return {
        "object_key": object_key,
        "original_filename": original_filename,
        "sha256": sha256,
        "byte_size": byte_size,
        "mime_type": mime_type,
    }


def success_envelope() -> dict[str, object]:
    return {
        "delivery_schema_version": "1.0",
        "delivery_id": "delivery-001",
        "job_id": "job-001",
        "attempt_no": 1,
        "status": "success",
        "processor_version": "3.0.0",
        "rule_set_sha256": SHA_A,
        "result_schema_version": "3.0",
        "business_date": "2026-07-27",
        "artifacts": {
            "source_xml": artifact(
                "arr/jobs/job-001/input/source.xml",
                "source.xml",
                SHA_A,
                100,
                "application/xml",
            ),
            "daily_report": artifact(
                "arr/jobs/job-001/output/7.27.xlsx",
                "7.27.xlsx",
                SHA_B,
                200,
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ),
            "result_json": artifact(
                "arr/jobs/job-001/output/result.json",
                "result.json",
                SHA_C,
                300,
                "application/json",
            ),
            "structured_result_json": artifact(
                "arr/jobs/job-001/output/structured-result.json",
                "structured-result.json",
                SHA_D,
                400,
                "application/json",
            ),
            "exception_report": None,
        },
    }


def failure_envelope() -> dict[str, object]:
    payload = success_envelope()
    payload["status"] = "failed"
    payload["business_date"] = None
    artifacts = payload["artifacts"]
    assert isinstance(artifacts, dict)
    artifacts["daily_report"] = None
    artifacts["exception_report"] = artifact(
        "arr/jobs/job-001/output/exception.xlsx",
        "exception.xlsx",
        SHA_E,
        500,
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    )
    return payload


class DeliveryEnvelopeTests(unittest.TestCase):
    def test_success_contract_is_strict_and_typed(self):
        envelope = DeliveryEnvelope.from_dict(success_envelope())
        self.assertEqual(envelope.job_id, "job-001")
        self.assertEqual(envelope.attempt_no, 1)
        self.assertEqual(envelope.business_date.isoformat(), "2026-07-27")
        self.assertEqual(envelope.artifacts["source_xml"].file_kind, "opera_xml")
        self.assertIsNone(envelope.artifacts["exception_report"])

    def test_failed_contract_requires_exception_and_forbids_daily(self):
        envelope = DeliveryEnvelope.from_dict(failure_envelope())
        self.assertEqual(envelope.status, "failed")
        self.assertIsNone(envelope.business_date)
        self.assertIsNone(envelope.artifacts["daily_report"])
        self.assertIsNotNone(envelope.artifacts["exception_report"])

        missing_exception = failure_envelope()
        artifacts = missing_exception["artifacts"]
        assert isinstance(artifacts, dict)
        artifacts["exception_report"] = None
        with self.assertRaisesRegex(IngestionError, "failed delivery"):
            DeliveryEnvelope.from_dict(missing_exception)

    def test_extra_fields_and_monthly_artifacts_are_rejected(self):
        extra = success_envelope()
        extra["message"] = "not allowed"
        with self.assertRaisesRegex(IngestionError, "field set"):
            DeliveryEnvelope.from_dict(extra)

        monthly = success_envelope()
        artifacts = monthly["artifacts"]
        assert isinstance(artifacts, dict)
        artifacts["monthly_report"] = artifact(
            "arr/jobs/job-001/output/monthly.xlsx",
            "monthly.xlsx",
            SHA_E,
            500,
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        )
        with self.assertRaisesRegex(IngestionError, "artifact field set"):
            DeliveryEnvelope.from_dict(monthly)

    def test_object_keys_and_filenames_cannot_escape_or_impersonate_roles(self):
        invalid_keys = (
            "/absolute/source.xml",
            "../source.xml",
            "arr/jobs/../source.xml",
            "arr\\jobs\\source.xml",
            "arr//jobs/source.xml",
        )
        for invalid in invalid_keys:
            payload = success_envelope()
            artifacts = payload["artifacts"]
            assert isinstance(artifacts, dict)
            source = artifacts["source_xml"]
            assert isinstance(source, dict)
            source["object_key"] = invalid
            with self.subTest(object_key=invalid):
                with self.assertRaises(IngestionError):
                    DeliveryEnvelope.from_dict(payload)

        bad_filename = success_envelope()
        artifacts = bad_filename["artifacts"]
        assert isinstance(artifacts, dict)
        daily = artifacts["daily_report"]
        assert isinstance(daily, dict)
        daily["original_filename"] = "../7.27.xlsx"
        with self.assertRaises(IngestionError):
            DeliveryEnvelope.from_dict(bad_filename)

        bad_mime = success_envelope()
        artifacts = bad_mime["artifacts"]
        assert isinstance(artifacts, dict)
        source = artifacts["source_xml"]
        assert isinstance(source, dict)
        source["mime_type"] = "text/plain"
        with self.assertRaisesRegex(IngestionError, "source_xml metadata"):
            DeliveryEnvelope.from_dict(bad_mime)

    def test_contract_rejects_bad_versions_hashes_dates_and_identifiers(self):
        mutations = (
            ("delivery_schema_version", "2.0"),
            ("delivery_id", "contains spaces"),
            ("attempt_no", 0),
            ("processor_version", ""),
            ("rule_set_sha256", "not-a-hash"),
            ("result_schema_version", "2.0"),
            ("business_date", "2026-02-30"),
        )
        for field, value in mutations:
            payload = copy.deepcopy(success_envelope())
            payload[field] = value
            with self.subTest(field=field):
                with self.assertRaises(IngestionError):
                    DeliveryEnvelope.from_dict(payload)


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