"""Pruebas de las reglas de negocio agregadas para cumplir el PDF de agente de carga.

Cubren: puntos de decisión y ciclo de corrección de la bitácora (R-E-13/14, R-I-06/07),
cierre operativo y gate de facturación (R-E-22 / R-F-01), envío y revisión de factura
(R-F-05/06) y continuidad comercial (R-C-02/04/12) + catálogo de Incoterms (R-T-10).
"""
from decimal import Decimal

import pytest
from fastapi import HTTPException

from api.v1.modules.crm.accounts import service as accounts_service
from api.v1.modules.crm.accounts.dto import AccountCreate
from api.v1.modules.crm.opportunities import service as opps_service
from api.v1.modules.crm.opportunities.dto import OpportunityCreate
from api.v1.modules.crm.quotes import service as quotes_service
from api.v1.modules.crm.quotes.dto import QuoteCreate, QuoteItemCreate
from api.v1.modules.crm.service_requests import service as sr_service
from api.v1.modules.crm.service_requests.dto import (
    ServiceRequestContactInput,
    ServiceRequestCreate,
    ServiceRequestFromOpportunityInput,
)
from api.v1.modules.fin.invoices import service as inv_service
from api.v1.modules.fin.invoices.dto import InvoiceClientReviewInput, InvoiceCreate, InvoiceItemCreate
from api.v1.modules.ops.shipments import service as ops_service
from api.v1.modules.ops.shipments.dto import (
    ShipmentCloseInput,
    ShipmentCreate,
    ShipmentEventDecisionInput,
    ShipmentRescheduleInput,
)

T, C = 1, 1


def _shipment(db, op_type="exportacion"):
    return ops_service.create_shipment(db, ShipmentCreate(reference="EMB-T", operation_type=op_type), T, C)


# ---------- Bitácora: decisiones y ciclo de corrección ----------

def test_seed_milestones_include_decision_points(db):
    sh = _shipment(db, "exportacion")
    events = ops_service.seed_default_milestones(db, sh.id, T, C)
    decisions = [e for e in events if e.kind == "decision"]
    assert any(e.event_type == "decision_cutoff" for e in decisions)
    assert any(e.event_type == "decision_despacho_exportacion" for e in decisions)


def test_decision_autorizado_completa(db):
    sh = _shipment(db, "importacion")
    events = ops_service.seed_default_milestones(db, sh.id, T, C)
    decision = next(e for e in events if e.kind == "decision")
    updated = ops_service.decide_shipment_event(db, decision.id, ShipmentEventDecisionInput(outcome="autorizado"), T, C)
    assert updated.status == "completado" and updated.outcome == "autorizado"


def test_decision_rechazado_abre_correccion(db):
    sh = _shipment(db, "importacion")
    events = ops_service.seed_default_milestones(db, sh.id, T, C)
    before = len(ops_service.get_shipment_events(db, T, C, sh.id))
    decision = next(e for e in events if e.kind == "decision")
    ops_service.decide_shipment_event(db, decision.id, ShipmentEventDecisionInput(outcome="rechazado", notes="Docs incompletos"), T, C)
    after = ops_service.get_shipment_events(db, T, C, sh.id)
    assert len(after) == before + 1  # se creó el hito de corrección
    correction = next(e for e in after if e.parent_event_id == decision.id)
    assert correction.status == "en_correccion" and correction.attempt == 2


def test_complete_on_decision_falla(db):
    sh = _shipment(db, "importacion")
    events = ops_service.seed_default_milestones(db, sh.id, T, C)
    decision = next(e for e in events if e.kind == "decision")
    with pytest.raises(HTTPException) as exc:
        ops_service.complete_shipment_event(db, decision.id, T, C)
    assert exc.value.status_code == 422


# ---------- Cierre operativo y gate de facturación ----------

def test_close_blocked_with_pending_decision(db):
    sh = _shipment(db, "importacion")
    ops_service.seed_default_milestones(db, sh.id, T, C)  # deja decisiones pendientes
    with pytest.raises(HTTPException) as exc:
        ops_service.close_shipment(db, sh.id, ShipmentCloseInput(actual_cost_total=Decimal("100")), T, C)
    assert exc.value.status_code == 409


def test_generate_invoice_requires_closed_shipment(db):
    sh = _shipment(db, "exportacion")  # abierta, sin cerrar
    with pytest.raises(HTTPException) as exc:
        inv_service.generate_from_shipment(db, sh.id, T, C)
    assert exc.value.status_code == 409
    # Tras cerrar (sin hitos → sin decisiones pendientes) sí factura
    ops_service.close_shipment(db, sh.id, ShipmentCloseInput(actual_cost_total=Decimal("500"), cost_currency="MXN"), T, C)
    inv = inv_service.generate_from_shipment(db, sh.id, T, C)
    assert inv.shipment_id == sh.id


def test_reschedule_keeps_previous_etd(db):
    from datetime import date
    sh = ops_service.create_shipment(db, ShipmentCreate(reference="EMB-R", operation_type="exportacion", etd=date(2026, 1, 10)), T, C)
    ops_service.reschedule_departure(db, sh.id, ShipmentRescheduleInput(etd=date(2026, 1, 20), reason="Cut Off perdido"), T, C)
    sh = ops_service.get_shipment(db, sh.id, T, C)
    assert str(sh.previous_etd) == "2026-01-10" and str(sh.etd) == "2026-01-20"


# ---------- Envío y revisión de factura ----------

def test_send_invoice_generates_pdf_and_reviews(db, monkeypatch):
    stored = {}
    monkeypatch.setattr("core.storage_s3.put_object_bytes", lambda key, body, content_type="": stored.update({"key": key, "len": len(body)}))
    acc = accounts_service.create_account(db, AccountCreate(name="Cliente PDF"), T, C)
    inv = inv_service.create_invoice(db, InvoiceCreate(reference="F-PDF", account_id=acc.id, tax_rate=Decimal("16")), T, C)
    inv_service.create_item(db, InvoiceItemCreate(invoice_id=inv.id, concept="flete_internacional", quantity=1, unit_amount=1000), T, C)
    inv_service.emit_invoice(db, inv.id, T, C)
    inv = inv_service.send_invoice(db, inv.id, T, C)
    assert inv.status == "enviada" and inv.pdf_file_key and stored["len"] > 0
    # Revisión del cliente: aprobada
    inv = inv_service.mark_client_review(db, inv.id, T, C)
    assert inv.status == "en_revision_cliente"
    inv = inv_service.client_review_decision(db, inv.id, InvoiceClientReviewInput(approved=True, notes="OK"), T, C)
    assert inv.status == "enviada" and inv.client_approved is True and inv.client_reviewed_at is not None


# ---------- Continuidad comercial ----------

def test_register_contact_sets_stage(db):
    acc = accounts_service.create_account(db, AccountCreate(name="Prospecto"), T, C)
    sr = sr_service.create_service_request(db, ServiceRequestCreate(account_id=acc.id, operation_type="exportacion"), T, C)
    sr = sr_service.register_contact(db, sr.id, ServiceRequestContactInput(notes="Primer contacto"), T, C)
    assert sr.status == "contacto" and sr.first_contact_at is not None


def test_service_request_from_opportunity_links_back(db):
    acc = accounts_service.create_account(db, AccountCreate(name="Cliente Op"), T, C)
    opp = opps_service.create_opportunity(db, OpportunityCreate(name="Oportunidad X", account_id=acc.id), T, C)
    sr = sr_service.create_from_opportunity(
        db, opp.id, ServiceRequestFromOpportunityInput(operation_type="importacion"), T, C
    )
    assert sr.opportunity_id == opp.id and sr.account_id == acc.id


def test_clone_quote_reopens_request(db):
    acc = accounts_service.create_account(db, AccountCreate(name="Cliente Q"), T, C)
    sr = sr_service.create_service_request(db, ServiceRequestCreate(account_id=acc.id, operation_type="exportacion"), T, C)
    q = quotes_service.create_quote(db, QuoteCreate(reference="COT-1", account_id=acc.id, service_request_id=sr.id, currency="USD"), T, C)
    quotes_service.create_quote_item(db, QuoteItemCreate(quote_id=q.id, concept="flete_internacional", quantity=1, unit_cost=100, unit_sale=200), T, C)
    quotes_service.reject_quote(db, q.id, T, C)
    clone = quotes_service.clone_quote(db, q.id, T, C)
    assert clone.id != q.id and clone.status == "borrador"
    items = quotes_service.get_quote_items(db, clone.id, T, C)
    assert len(items) == 1
    sr = sr_service.get_service_request(db, sr.id, T, C)
    assert sr.status == "en_analisis"  # reabierta para re-cotizar


def test_incoterm_catalog_validation(db):
    acc = accounts_service.create_account(db, AccountCreate(name="Cliente Inc"), T, C)
    with pytest.raises(HTTPException) as exc:
        sr_service.create_service_request(
            db, ServiceRequestCreate(account_id=acc.id, operation_type="exportacion", incoterm="XXX"), T, C
        )
    assert exc.value.status_code == 422
    ok = sr_service.create_service_request(
        db, ServiceRequestCreate(account_id=acc.id, operation_type="exportacion", incoterm="FOB"), T, C
    )
    assert ok.incoterm == "FOB"
