"""End-to-end ordering flow test exercising the acceptance criteria.

Drives the real FastAPI app via TestClient against an in-memory database, using
directly-issued auth tokens (bypassing the password-based login endpoints).
"""
from __future__ import annotations

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, select
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

from app.core.access import INTERNAL_USER_SUBJECT
from app.core.config import settings
from app.core.security import issue_token
from app.db.session import Base, get_db
from app.main import app
from app.models.access import User
from app.models.client_access import ClientUser
from app.seed_access import seed_access
from app.services import ordering_service as svc

ORDERING_TENANT = svc.ORDERING_TENANT


@pytest.fixture()
def client():
    engine = create_engine(
        "sqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(bind=engine)
    TestingSession = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)

    def override_get_db():
        db = TestingSession()
        try:
            yield db
        finally:
            db.close()

    app.dependency_overrides[get_db] = override_get_db
    with TestClient(app) as test_client:
        test_client._session_factory = TestingSession  # type: ignore[attr-defined]
        yield test_client
    app.dependency_overrides.clear()


def _admin_headers() -> dict[str, str]:
    token = issue_token({"name": "Admin", "email": settings.admin_email, "role": "admin"})
    return {"Authorization": f"Bearer {token}"}


def _customer_headers(db_session_factory, *, account_id: int) -> dict[str, str]:
    db = db_session_factory()
    user = db.scalar(select(ClientUser).where(ClientUser.client_account_id == account_id))
    payload = {
        "name": user.full_name,
        "email": user.email,
        "role": "client",
        "tenant_id": user.tenant_id,
        "client_role": user.role,
        "user_id": user.id,
        "client_account_id": user.client_account_id,
    }
    db.close()
    token = issue_token(payload)
    return {"Authorization": f"Bearer {token}"}


def test_full_b2b_ordering_flow(client):
    admin = _admin_headers()

    # 1. Admin creates a customer.
    r = client.post("/api/ordering-admin/customers", headers=admin, json={"name": "Acme Farms", "client_code": "ACME"})
    assert r.status_code == 201, r.text
    customer = r.json()
    customer_id = customer["id"]

    # 2. Admin creates a user for that customer.
    r = client.post(
        f"/api/ordering-admin/customers/{customer_id}/users",
        headers=admin,
        json={"full_name": "Buyer Bob", "email": "bob@acme.test", "role": "buyer"},
    )
    assert r.status_code == 201, r.text

    # 3. Admin creates products.
    r = client.post(
        "/api/ordering-admin/products",
        headers=admin,
        json={"name": "Maize 1T", "sku": "MAIZE-1T", "category": "grains", "base_price": 100.0, "min_order_quantity": 1},
    )
    assert r.status_code == 201, r.text
    product = r.json()
    product_id = product["id"]

    r = client.post(
        "/api/ordering-admin/products",
        headers=admin,
        json={"name": "Hidden Blend", "sku": "HIDDEN-1", "category": "custom_blends", "base_price": 50.0},
    )
    hidden_product_id = r.json()["id"]

    # 4. Admin assigns a customer-specific price (contract @ 92.5).
    r = client.put(
        f"/api/ordering-admin/customers/{customer_id}/product-prices",
        headers=admin,
        json={"product_id": product_id, "unit_price": 92.5, "rule_type": "contract"},
    )
    assert r.status_code == 200, r.text

    # 4b. Admin hides the second product from this customer.
    r = client.put(
        f"/api/ordering-admin/customers/{customer_id}/visibility",
        headers=admin,
        json={"product_id": hidden_product_id, "visible": False},
    )
    assert r.status_code == 200, r.text

    # 5. Customer logs in (token) and sees only their available product + price.
    cust = _customer_headers(client._session_factory, account_id=customer_id)
    r = client.get("/api/ordering/catalogue", headers=cust)
    assert r.status_code == 200, r.text
    catalogue = r.json()
    skus = {p["sku"] for p in catalogue}
    assert skus == {"MAIZE-1T"}  # hidden product is not visible
    maize = catalogue[0]
    assert maize["price"]["unit_price"] == 92.5
    assert maize["price"]["price_source"] == "contract"

    # 6. Customer creates a draft order.
    r = client.post(
        "/api/ordering/orders",
        headers=cust,
        json={"lines": [{"product_id": product_id, "quantity": 3}], "fulfilment_method": "delivery"},
    )
    assert r.status_code == 201, r.text
    draft = r.json()
    assert draft["status"] == "draft"
    assert draft["subtotal_ex_gst"] == 277.5  # 3 * 92.5
    order_id = draft["id"]

    # Minimum-order-quantity validation.
    r = client.post(
        "/api/ordering/orders",
        headers=cust,
        json={"lines": [{"product_id": product_id, "quantity": 0.5}]},
    )
    # quantity below MOQ of 1 → caught by schema (gt=0 ok) then MOQ check 422.
    assert r.status_code == 422

    # 7. Customer submits the order — price is frozen on the line.
    r = client.post(f"/api/ordering/orders/{order_id}/submit", headers=cust, json={"purchase_order_number": "PO-77"})
    assert r.status_code == 200, r.text
    submitted = r.json()
    assert submitted["status"] == "submitted"
    assert submitted["order_number"].startswith("ORD-")
    assert submitted["lines"][0]["unit_price"] == 92.5
    assert submitted["purchase_order_number"] == "PO-77"

    # Customer can no longer edit a submitted order.
    r = client.patch(f"/api/ordering/orders/{order_id}", headers=cust, json={"delivery_notes": "late edit"})
    assert r.status_code == 409

    # 8. Admin sees the submitted order and manages it.
    r = client.get("/api/ordering-admin/orders", headers=admin)
    assert r.status_code == 200, r.text
    admin_orders = r.json()
    assert any(o["id"] == order_id for o in admin_orders)
    assert admin_orders[0]["customer_name"] == "Acme Farms"

    # Status progression with lifecycle enforcement.
    r = client.patch(f"/api/ordering-admin/orders/{order_id}/status", headers=admin, json={"to_status": "confirmed"})
    assert r.status_code == 200, r.text
    # Illegal jump is rejected.
    r = client.patch(f"/api/ordering-admin/orders/{order_id}/status", headers=admin, json={"to_status": "completed"})
    assert r.status_code == 409

    # 9. Xero submission (stub) succeeds and records an invoice id.
    r = client.post(f"/api/ordering-admin/orders/{order_id}/send-to-xero", headers=admin)
    assert r.status_code == 200, r.text
    xero = r.json()
    assert xero["xero_result"]["status"] == "success"
    assert xero["xero_result"]["stubbed"] is True
    assert xero["xero_invoice_id"].startswith("STUB-INV-")
    assert xero["raw_status"] == "sent_to_xero"


def test_customer_isolation_between_companies(client):
    admin = _admin_headers()
    # Two customers, each with a user.
    a = client.post("/api/ordering-admin/customers", headers=admin, json={"name": "Alpha", "client_code": "ALPHA"}).json()
    b = client.post("/api/ordering-admin/customers", headers=admin, json={"name": "Beta", "client_code": "BETA"}).json()
    client.post(f"/api/ordering-admin/customers/{a['id']}/users", headers=admin, json={"full_name": "A1", "email": "a1@alpha.test", "role": "owner"})
    client.post(f"/api/ordering-admin/customers/{b['id']}/users", headers=admin, json={"full_name": "B1", "email": "b1@beta.test", "role": "owner"})
    product = client.post("/api/ordering-admin/products", headers=admin, json={"name": "Barley 1T", "sku": "BAR-1T", "category": "grains", "base_price": 80.0}).json()

    headers_a = _customer_headers(client._session_factory, account_id=a["id"])
    headers_b = _customer_headers(client._session_factory, account_id=b["id"])

    # Alpha creates and submits an order.
    order = client.post("/api/ordering/orders", headers=headers_a, json={"lines": [{"product_id": product["id"], "quantity": 2}]}).json()
    # Beta must not be able to read Alpha's order.
    r = client.get(f"/api/ordering/orders/{order['id']}", headers=headers_b)
    assert r.status_code == 404
    # Alpha can.
    r = client.get(f"/api/ordering/orders/{order['id']}", headers=headers_a)
    assert r.status_code == 200


def test_internal_staff_can_manage_orders(client):
    # Internal Hunter Stock Feeds staff (not the legacy /admin login) manage the
    # ordering portal via the client/internal session + manage_ordering perm.
    db = client._session_factory()
    seed_access(db)
    db.commit()
    admin_user = db.query(User).filter_by(email="admin@hunterstockfeeds.com").one()
    user_id = admin_user.id
    db.close()

    token = issue_token({"sub": INTERNAL_USER_SUBJECT, "user_id": user_id})
    headers = {"Authorization": f"Bearer {token}"}

    # Can list customers and create catalogue products through the admin API.
    assert client.get("/api/ordering-admin/customers", headers=headers).status_code == 200
    r = client.post(
        "/api/ordering-admin/products",
        headers=headers,
        json={"name": "Staff Wheat", "sku": "WHEAT-STAFF", "category": "grains", "base_price": 30.0},
    )
    assert r.status_code == 201, r.text


def test_disabled_customer_cannot_order(client):
    admin = _admin_headers()
    cust = client.post("/api/ordering-admin/customers", headers=admin, json={"name": "Gamma", "client_code": "GAMMA"}).json()
    client.post(f"/api/ordering-admin/customers/{cust['id']}/users", headers=admin, json={"full_name": "G1", "email": "g1@gamma.test", "role": "buyer"})
    client.post("/api/ordering-admin/products", headers=admin, json={"name": "Oats 1T", "sku": "OATS-1T", "category": "grains", "base_price": 70.0})

    # Disable the customer account.
    r = client.patch(f"/api/ordering-admin/customers/{cust['id']}", headers=admin, json={"status": "disabled"})
    assert r.status_code == 200

    headers = _customer_headers(client._session_factory, account_id=cust["id"])
    r = client.get("/api/ordering/catalogue", headers=headers)
    assert r.status_code == 403
