"""Sales pipeline <-> Records sync (plot_record_sync_service).

A plot's status and its linked record's interment type must stay in sync
regardless of which side drives the change — the Edit Plot page (covered by
tests/apps/plots/test_plot_vacate_confirm.py) or the sales pipeline (covered
here): signing a contract that reserves the plot must promote the record off
"pre-need", and losing a held-plot deal must release the plot and remove the
record it was reserved for.
"""
from __future__ import annotations

import uuid

import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.plots.models.plot import Plot
from src.apps.records.models.burial_info import BurialInfo
from src.apps.records.models.record import Record
from src.apps.sales.models.opportunity import Opportunity
from src.apps.sections.models.section import Section

pytestmark = pytest.mark.asyncio

PROPOSALS_URL = "/api/v1/sales/proposals"
CONTRACTS_URL = "/api/v1/sales/contracts"
INVOICES_URL = "/api/v1/billing/invoices"
OPPS_URL = "/api/v1/sales/opportunities"

FAKE_SIG = (
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk"
    "+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
)


def _headers(token: str, tenant_id) -> dict:
    return {"Authorization": f"Bearer {token}", "X-Tenant-ID": str(tenant_id)}


async def _make_plot(db: AsyncSession, account, status: str = "hold") -> Plot:
    ref = f"P-{uuid.uuid4().hex[:6]}"
    section = Section(tenant_id=account.id, code=ref[0], name=f"Section {ref[0]}")
    db.add(section)
    await db.flush()
    plot = Plot(tenant_id=account.id, plot_ref=ref, section_id=section.id, status=status)
    db.add(plot)
    await db.flush()
    return plot


async def _make_record_with_burial(
    db: AsyncSession, account, plot: Plot, interment_type: str = "pre_need_no_service_yet",
) -> Record:
    record = Record(
        tenant_id=account.id, plot_id=plot.id, first_name="Pat", last_name="Nguyen", status="active",
    )
    db.add(record)
    await db.flush()
    db.add(BurialInfo(record_id=record.id, tenant_id=account.id, interment_type=interment_type))
    await db.flush()
    return record


async def _create_contract(client, token, tenant_id, plot_id, unit_price="2000.00") -> dict:
    resp = await client.post(
        CONTRACTS_URL,
        json={
            "plot_id": str(plot_id),
            "contract_type": "pre_need",
            "purchaser_name": "Mary McLeod",
            "purchaser_email": "buyer@example.ca",
            "payment_plan_type": "full",
            "line_items": [
                {"description": "Burial plot", "quantity": 1, "unit_price": unit_price},
            ],
        },
        headers=_headers(token, tenant_id),
    )
    assert resp.status_code == 201, resp.text
    return resp.json()["data"]


async def _issue_contract(client, token, tenant_id, contract_id) -> dict:
    resp = await client.patch(
        f"{CONTRACTS_URL}/{contract_id}/issue", headers=_headers(token, tenant_id)
    )
    assert resp.status_code == 200, resp.text
    return resp.json()["data"]


async def _sign_contract(client, token, tenant_id, contract_id) -> None:
    resp = await client.post(
        f"{CONTRACTS_URL}/{contract_id}/sign",
        json={"purchaser_signature_b64": FAKE_SIG},
        headers=_headers(token, tenant_id),
    )
    assert resp.status_code == 200, resp.text


async def _pay_invoice(client, token, tenant_id, invoice_id, amount) -> None:
    resp = await client.post(
        f"{INVOICES_URL}/{invoice_id}/payment",
        json={"amount": str(amount), "payment_date": "2026-08-25", "payment_method": "Cash"},
        headers=_headers(token, tenant_id),
    )
    assert resp.status_code == 201, resp.text


async def test_signed_and_paid_contract_reserves_plot_and_syncs_record(
    client: AsyncClient, admin_token: str, test_account, db_session: AsyncSession,
):
    plot = await _make_plot(db_session, test_account, status="hold")
    record = await _make_record_with_burial(db_session, test_account, plot)

    contract = await _create_contract(client, admin_token, test_account.id, plot.id, unit_price="2000.00")
    issued = await _issue_contract(client, admin_token, test_account.id, contract["id"])
    await _sign_contract(client, admin_token, test_account.id, contract["id"])
    invoice_id = issued["invoices_created"][0]["id"]
    await _pay_invoice(client, admin_token, test_account.id, invoice_id, "2000.00")

    await db_session.refresh(plot)
    assert plot.status == "reserved"

    result = await db_session.execute(select(BurialInfo).where(BurialInfo.record_id == record.id))
    burial = result.scalar_one()
    assert burial.interment_type == "burial"


async def test_mark_lost_releases_held_plot_and_deletes_its_record(
    client: AsyncClient, admin_token: str, test_account, db_session: AsyncSession,
):
    plot = await _make_plot(db_session, test_account, status="hold")
    record = await _make_record_with_burial(db_session, test_account, plot)

    opp = Opportunity(
        tenant_id=test_account.id, plot_id=plot.id, stage="contract_sent", family_name="Pat Nguyen",
    )
    db_session.add(opp)
    await db_session.flush()

    resp = await client.post(
        f"{OPPS_URL}/{opp.id}/mark-lost",
        json={"loss_reason": "timing"},
        headers=_headers(admin_token, test_account.id),
    )
    assert resp.status_code == 200, resp.text

    await db_session.refresh(plot)
    assert plot.status == "vacant"

    await db_session.refresh(record)
    assert record.deleted_at is not None


async def test_mark_lost_leaves_reserved_plot_and_record_untouched(
    client: AsyncClient, admin_token: str, test_account, db_session: AsyncSession,
):
    """LOST_ELIGIBLE_STAGES already blocks mark-lost once a contract is signed
    (plot would be reserved/occupied by then) — confirms the record-deletion
    cascade can never fire against a plot that isn't on hold."""
    plot = await _make_plot(db_session, test_account, status="reserved")
    record = await _make_record_with_burial(db_session, test_account, plot, interment_type="burial")

    opp = Opportunity(
        tenant_id=test_account.id, plot_id=plot.id, stage="contract_signed", family_name="Pat Nguyen",
    )
    db_session.add(opp)
    await db_session.flush()

    resp = await client.post(
        f"{OPPS_URL}/{opp.id}/mark-lost",
        json={"loss_reason": "timing"},
        headers=_headers(admin_token, test_account.id),
    )
    assert resp.status_code == 422, resp.text

    await db_session.refresh(plot)
    assert plot.status == "reserved"
    await db_session.refresh(record)
    assert record.deleted_at is None
