"""
Tests for the Creation API spec-gap fixes (Step 6)

These tests verify:
1. secondaryReferenceId is optional: the order is keyed and correlated by
   primaryReferenceId when Amazon omits it (spec: "if provided")
2. trackingId and isTestData are persisted with the stored request
3. Ingress failures acknowledge as HTTP 200 + ERRORED (errorMessage
   capped at 300 chars) instead of HTTP 4xx/5xx; auth stays 401
4. Every rxDetailForConsultation entry contributes medicines, not just
   the first prescription
"""

import asyncio
import sys
import unittest
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))

from fastapi import FastAPI
from fastapi.testclient import TestClient

import app.api.routers.connector as connector_mod
import app.src.file_processor as file_processor_mod
from app.src.file_processor import FileProcessor
from app.src.json_mapper import JSONMapper


class MockConfig:
    def __init__(self):
        self.rx_api_key = "test-api-key"
        self.rx_api_base_url = "http://localhost:10000/api/1rx/v1"
        self.rx_api_retry_attempts = 1
        self.rx_api_retry_delay_seconds = 0.01
        self.rx_api_timeout_seconds = 1.0
        self.ingress_mode = "api"


def make_amazon_request(secondary="cons-123", tracking_id="trk-1", is_test=False):
    request = {
        "messageIdentifiers": {
            "primaryReferenceId": "404-1234567#a1b2",
            "trackingId": tracking_id,
            "isTestData": is_test,
        },
        "data": {
            "requestMetadata": {"messageId": "msg-1", "messageSentTime": 1699968883231},
            "rxDetailForConsultation": [
                {
                    "rxHeaderDetailsForConsultation": {
                        "rxId": "rx-1",
                        "patientContactDetails": {
                            "name": "Test Patient",
                            "contactNumber": "+919999999999",
                        },
                    },
                    "rxItemDetailForConsultation": [
                        {"rxItemId": "ITEM-1", "medicineName": "Med One"}
                    ],
                }
            ],
        },
    }
    if secondary is not None:
        request["messageIdentifiers"]["secondaryReferenceId"] = secondary
    return request


class TestOptionalSecondaryReferenceId(unittest.TestCase):
    def setUp(self):
        self.store = MagicMock()
        self.store.store_request.return_value = True
        with patch.object(file_processor_mod, "SFTPHandler"), patch.object(
            file_processor_mod, "PGPHandler"
        ):
            self.fp = FileProcessor(MockConfig(), self.store)
        self.fp.call_1rx_api = AsyncMock(return_value={"data": {"orderId": "1"}})

    def test_falls_back_to_primary_reference_id(self):
        result = asyncio.run(
            self.fp.process_api_request(make_amazon_request(secondary=None))
        )

        self.assertTrue(result["success"])
        self.assertEqual(result["consultation_id"], "404-1234567#a1b2")
        stored = self.store.store_request.call_args[0][0]
        self.assertEqual(stored["consultation_id"], "404-1234567#a1b2")
        # The 1Rx payload's shipmentCode must match the stored key, since
        # the webhook correlates by shipmentCode.
        self.assertEqual(stored["rx_request"]["shipmentCode"], "404-1234567#a1b2")

    def test_secondary_still_wins_when_present(self):
        result = asyncio.run(self.fp.process_api_request(make_amazon_request()))

        self.assertEqual(result["consultation_id"], "cons-123")
        stored = self.store.store_request.call_args[0][0]
        self.assertEqual(stored["rx_request"]["shipmentCode"], "cons-123")

    def test_tracking_id_and_test_flag_are_persisted(self):
        asyncio.run(
            self.fp.process_api_request(
                make_amazon_request(tracking_id="trk-99", is_test=True)
            )
        )

        stored = self.store.store_request.call_args[0][0]
        self.assertEqual(stored["tracking_id"], "trk-99")
        self.assertIs(stored["is_test_data"], True)

    def test_missing_both_reference_ids_is_an_error(self):
        request = make_amazon_request(secondary=None)
        del request["messageIdentifiers"]["primaryReferenceId"]

        result = asyncio.run(self.fp.process_api_request(request))

        self.assertFalse(result["success"])
        self.assertIn("primaryReferenceId", result["error"])


class TestMultiPrescriptionMapping(unittest.TestCase):
    def test_medicines_come_from_every_prescription(self):
        request = make_amazon_request()
        request["data"]["rxDetailForConsultation"].append(
            {
                "rxHeaderDetailsForConsultation": {"rxId": "rx-2"},
                "rxItemDetailForConsultation": [
                    {"rxItemId": "ITEM-2", "medicineName": "Med Two"},
                    {"rxItemId": "ITEM-3", "medicineName": "Med Three"},
                ],
            }
        )

        rx_payload = JSONMapper.amazon_api_to_1rx(request)

        ids = [m["id"] for m in rx_payload["medicines"]]
        self.assertEqual(ids, ["ITEM-1", "ITEM-2", "ITEM-3"])

    def test_patient_contact_found_even_when_first_entry_lacks_it(self):
        request = make_amazon_request()
        first = request["data"]["rxDetailForConsultation"][0]
        contact = first["rxHeaderDetailsForConsultation"].pop("patientContactDetails")
        request["data"]["rxDetailForConsultation"].insert(0, {
            "rxHeaderDetailsForConsultation": {"rxId": "rx-0"},
            "rxItemDetailForConsultation": [],
        })
        first["rxHeaderDetailsForConsultation"]["patientContactDetails"] = contact

        rx_payload = JSONMapper.amazon_api_to_1rx(request)

        self.assertEqual(rx_payload["customer"]["name"], "Test Patient")


class TestIngressAcknowledgement(unittest.TestCase):
    def setUp(self):
        self.app = FastAPI()
        self.app.include_router(connector_mod.router, prefix="/connector/amazon")
        self.fp = MagicMock()
        self.fp.config = MockConfig()
        self.fp.process_api_request = AsyncMock(
            return_value={
                "success": True,
                "consultation_id": "cons-123",
                "message_identifiers": {"trackingId": "trk-1"},
            }
        )
        self.app.state.file_processor = self.fp
        self.client = TestClient(self.app, raise_server_exceptions=False)
        patcher = patch.object(
            connector_mod,
            "validate_bearer_token",
            return_value={"valid": True, "payload": {"vendorId": "amazon"}},
        )
        self.mock_auth = patcher.start()
        self.addCleanup(patcher.stop)

    def post(self, body):
        return self.client.post(
            "/connector/amazon/orders",
            json=body,
            headers={"Authorization": "Bearer token"},
        )

    def test_success_returns_created(self):
        response = self.post(make_amazon_request())

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["consultationCreationStatus"], "CREATED")

    def test_missing_tracking_id_is_200_errored(self):
        body = make_amazon_request()
        del body["messageIdentifiers"]["trackingId"]

        response = self.post(body)

        self.assertEqual(response.status_code, 200)
        payload = response.json()
        self.assertEqual(payload["consultationCreationStatus"], "ERRORED")
        self.assertIn("trackingId", payload["errorMessage"])

    def test_unexpected_exception_is_200_errored_with_capped_message(self):
        self.fp.process_api_request.side_effect = RuntimeError("boom " * 200)

        response = self.post(make_amazon_request())

        self.assertEqual(response.status_code, 200)
        payload = response.json()
        self.assertEqual(payload["consultationCreationStatus"], "ERRORED")
        self.assertLessEqual(len(payload["errorMessage"]), 300)
        self.assertEqual(
            payload["messageIdentifiers"]["trackingId"], "trk-1"
        )

    def test_processing_failure_is_200_errored(self):
        self.fp.process_api_request.return_value = {
            "success": False,
            "error": "Failed to send to 1Rx API",
            "message_identifiers": {"trackingId": "trk-1"},
        }

        response = self.post(make_amazon_request())

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["consultationCreationStatus"], "ERRORED")

    def test_invalid_token_stays_401(self):
        self.mock_auth.return_value = {"valid": False, "error": "Token has expired"}

        response = self.post(make_amazon_request())

        self.assertEqual(response.status_code, 401)

    def test_ingress_disabled_stays_403(self):
        self.fp.config.ingress_mode = "sftp"

        response = self.post(make_amazon_request())

        self.assertEqual(response.status_code, 403)


if __name__ == "__main__":
    unittest.main()
