import uuid
from concurrent.futures import ThreadPoolExecutor

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import delete, select

from app.core.security import create_access_token, hash_password
from app.db.session import SessionLocal
from app.main import app
from app.models.branch import Branch
from app.models.business import Business
from app.models.business_creation_request import BusinessCreationRequest
from app.models.membership import Membership, MembershipBranch
from app.models.inventory import BranchInventory, InventoryTransfer, StockAdjustment
from app.models.product import Product, ProductCategory, ProductUnitConversion, ProductVariant, Unit
from app.models.customer import Customer
from app.models.supplier import Supplier
from app.models.purchase import Purchase, PurchaseItem, PurchasePayment
from app.models.sale import Sale, SaleItem, SalePayment, SaleIdempotency
from app.models.receivable import Receivable, ReceivableCollection
from app.models.receivable import DebtReminder
from app.models.invoice import Invoice, InvoiceItem, InvoicePayment
from app.models.quote import Quote, QuoteItem
from app.models.expense_income import Expense, ExpenseCategory, OtherIncome, OtherIncomeCategory
from app.models.payroll import PayrollProfile, PayrollRun
from app.models.delivery import Delivery, DeliveryStatusHistory
from app.models.loyalty import LoyaltySettings, LoyaltyTransaction
from app.models.user import User


def _headers(user: User) -> dict[str, str]:
    return {
        "Authorization": f"Bearer {create_access_token(str(user.id))}",
    }


def _payload(name: str = "Ada Stores") -> dict[str, str]:
    return {
        "name": name,
        "phone": "",
        "email": "",
        "address": "",
        "city": "",
        "state": "",
        "country": "Nigeria",
        "currency": "NGN",
        "default_language": "en",
        "main_branch_name": "Main Store",
    }


@pytest.fixture
def verified_user():
    marker = uuid.uuid4().hex
    db = SessionLocal()
    user = User(
        full_name="Workspace Test User",
        email=f"workspace-{marker}@example.com",
        password_hash=hash_password("TestPassword123!"),
        is_email_verified=True,
    )
    db.add(user)
    db.commit()
    db.refresh(user)

    try:
        yield user
    finally:
        memberships = list(
            db.scalars(select(Membership).where(Membership.user_id == user.id))
        )
        business_ids = [membership.business_id for membership in memberships]
        if business_ids:
            db.execute(
                delete(BusinessCreationRequest).where(
                    BusinessCreationRequest.user_id == user.id
                )
            )
            db.execute(delete(InventoryTransfer).where(InventoryTransfer.business_id.in_(business_ids)))
            db.execute(delete(StockAdjustment).where(StockAdjustment.business_id.in_(business_ids)))
            db.execute(delete(BranchInventory).where(BranchInventory.business_id.in_(business_ids)))
            purchase_ids = select(Purchase.id).where(Purchase.business_id.in_(business_ids))
            db.execute(delete(PurchasePayment).where(PurchasePayment.purchase_id.in_(purchase_ids)))
            db.execute(delete(PurchaseItem).where(PurchaseItem.purchase_id.in_(purchase_ids)))
            db.execute(delete(Purchase).where(Purchase.id.in_(purchase_ids)))
            sale_ids = select(Sale.id).where(Sale.business_id.in_(business_ids))
            db.execute(delete(LoyaltyTransaction).where(LoyaltyTransaction.business_id.in_(business_ids)))
            db.execute(delete(LoyaltySettings).where(LoyaltySettings.business_id.in_(business_ids)))
            receivable_ids = select(Receivable.id).where(Receivable.business_id.in_(business_ids))
            invoice_ids = select(Invoice.id).where(Invoice.business_id.in_(business_ids))
            quote_ids = select(Quote.id).where(Quote.business_id.in_(business_ids))
            db.execute(delete(QuoteItem).where(QuoteItem.quote_id.in_(quote_ids)))
            db.execute(delete(Quote).where(Quote.id.in_(quote_ids)))
            delivery_ids = select(Delivery.id).where(Delivery.business_id.in_(business_ids))
            db.execute(delete(DeliveryStatusHistory).where(DeliveryStatusHistory.delivery_id.in_(delivery_ids)))
            db.execute(delete(Delivery).where(Delivery.id.in_(delivery_ids)))
            db.execute(delete(PayrollRun).where(PayrollRun.business_id.in_(business_ids)))
            db.execute(delete(PayrollProfile).where(PayrollProfile.business_id.in_(business_ids)))
            db.execute(delete(Expense).where(Expense.business_id.in_(business_ids)))
            db.execute(delete(OtherIncome).where(OtherIncome.business_id.in_(business_ids)))
            db.execute(delete(ExpenseCategory).where(ExpenseCategory.business_id.in_(business_ids)))
            db.execute(delete(OtherIncomeCategory).where(OtherIncomeCategory.business_id.in_(business_ids)))
            db.execute(delete(InvoicePayment).where(InvoicePayment.invoice_id.in_(invoice_ids)))
            db.execute(delete(InvoiceItem).where(InvoiceItem.invoice_id.in_(invoice_ids)))
            db.execute(delete(DebtReminder).where(DebtReminder.business_id.in_(business_ids)))
            db.execute(delete(ReceivableCollection).where(ReceivableCollection.receivable_id.in_(receivable_ids)))
            db.execute(delete(Receivable).where(Receivable.id.in_(receivable_ids)))
            db.execute(delete(Invoice).where(Invoice.id.in_(invoice_ids)))
            db.execute(delete(SaleIdempotency).where(SaleIdempotency.sale_id.in_(sale_ids)))
            db.execute(delete(SalePayment).where(SalePayment.sale_id.in_(sale_ids)))
            db.execute(delete(SaleItem).where(SaleItem.sale_id.in_(sale_ids)))
            db.execute(delete(Sale).where(Sale.id.in_(sale_ids)))
            db.execute(delete(ProductUnitConversion).where(ProductUnitConversion.product_id.in_(select(Product.id).where(Product.business_id.in_(business_ids)))))
            db.execute(delete(ProductVariant).where(ProductVariant.business_id.in_(business_ids)))
            db.execute(delete(Product).where(Product.business_id.in_(business_ids)))
            db.execute(delete(ProductCategory).where(ProductCategory.business_id.in_(business_ids)))
            db.execute(delete(Unit).where(Unit.business_id.in_(business_ids)))
            db.execute(delete(Customer).where(Customer.business_id.in_(business_ids)))
            db.execute(delete(Supplier).where(Supplier.business_id.in_(business_ids)))
            db.execute(
                delete(MembershipBranch).where(
                    MembershipBranch.membership_id.in_(
                        [membership.id for membership in memberships]
                    )
                )
            )
            db.execute(delete(Membership).where(Membership.user_id == user.id))
            db.execute(delete(Branch).where(Branch.business_id.in_(business_ids)))
            db.execute(delete(Business).where(Business.id.in_(business_ids)))
        db.execute(delete(User).where(User.id == user.id))
        db.commit()
        db.close()


@pytest.fixture
def client():
    with TestClient(app) as test_client:
        yield test_client


def test_unauthenticated_onboarding_is_rejected(client):
    response = client.post("/api/businesses/onboarding", json=_payload())

    assert response.status_code == 401


def test_user_can_create_workspace_with_main_branch_and_ceo_membership(
    client, verified_user
):
    response = client.post(
        "/api/businesses/onboarding",
        json=_payload(),
        headers=_headers(verified_user),
    )

    assert response.status_code == 201
    body = response.json()
    assert body["business"]["name"] == "Ada Stores"
    assert body["membership"]["role"] == "CEO"
    assert body["membership"]["all_branches"] is True
    assert body["role"] == "CEO"
    assert len(body["accessible_branches"]) == 1
    assert body["accessible_branches"][0]["name"] == "Main Store"
    assert body["accessible_branches"][0]["is_main"] is True


def test_duplicate_onboarding_is_rejected(client, verified_user):
    headers = _headers(verified_user)
    assert client.post(
        "/api/businesses/onboarding", json=_payload(), headers=headers
    ).status_code == 201

    response = client.post(
        "/api/businesses/onboarding", json=_payload("Other Store"), headers=headers
    )

    assert response.status_code == 409


def test_workspace_is_scoped_to_current_user(client, verified_user):
    headers = _headers(verified_user)
    created = client.post(
        "/api/businesses/onboarding", json=_payload(), headers=headers
    )
    assert created.status_code == 201

    workspace = client.get(
        f"/api/businesses/{created.json()['business']['id']}/workspace", headers=headers
    )
    assert workspace.status_code == 200
    assert workspace.json()["business"]["id"] == created.json()["business"]["id"]

    other_user = User(
        full_name="Other User",
        email=f"other-{uuid.uuid4().hex}@example.com",
        password_hash=hash_password("TestPassword123!"),
        is_email_verified=True,
    )
    db = SessionLocal()
    db.add(other_user)
    db.commit()
    db.refresh(other_user)
    try:
        other_workspace = client.get(
            f"/api/businesses/{created.json()['business']['id']}/workspace",
            headers=_headers(other_user),
        )
        assert other_workspace.status_code == 404
    finally:
        db.execute(delete(User).where(User.id == other_user.id))
        db.commit()
        db.close()


def test_user_can_belong_to_multiple_businesses_and_list_only_their_workspaces(
    client, verified_user
):
    headers = _headers(verified_user)
    created = client.post("/api/businesses/onboarding", json=_payload(), headers=headers)
    assert created.status_code == 201

    second = client.post(
        "/api/businesses", json=_payload("Second Store"), headers={
            **headers, "Idempotency-Key": "second-business-key"
        },
    )
    third = client.post(
        "/api/businesses", json=_payload("Third Store"), headers={
            **headers, "Idempotency-Key": "third-business-key"
        },
    )
    assert second.status_code == 201
    assert third.status_code == 201

    db = SessionLocal()
    unrelated_business = Business(name="Other User Store")
    other_user = User(
        full_name="Unrelated User",
        email=f"unrelated-{uuid.uuid4().hex}@example.com",
        password_hash=hash_password("TestPassword123!"),
        is_email_verified=True,
    )
    db.add_all([unrelated_business, other_user])
    db.flush()
    db.add(Membership(business_id=unrelated_business.id, user_id=other_user.id))
    db.commit()
    try:
        response = client.get("/api/businesses/my-businesses", headers=headers)
        assert response.status_code == 200
        assert {item["business"]["name"] for item in response.json()["businesses"]} == {
            "Ada Stores", "Second Store", "Third Store"
        }
    finally:
        db.execute(delete(User).where(User.id == other_user.id))
        db.execute(delete(Business).where(Business.id == unrelated_business.id))
        db.commit()
        db.close()


def test_concurrent_additional_business_requests_with_same_key_are_idempotent(client, verified_user):
    headers = _headers(verified_user)
    assert client.post(
        "/api/businesses/onboarding", json=_payload(), headers=headers
    ).status_code == 201

    request_headers = {**headers, "Idempotency-Key": "repeat-business-key"}
    def create_business():
        with TestClient(app) as concurrent_client:
            return concurrent_client.post(
                "/api/businesses", json=_payload("Repeat Store"), headers=request_headers
            )

    with ThreadPoolExecutor(max_workers=2) as executor:
        first, second = list(executor.map(lambda _: create_business(), range(2)))

    assert {first.status_code, second.status_code} == {200, 201}
    assert first.json()["business"]["id"] == second.json()["business"]["id"]
    listed = client.get("/api/businesses/my-businesses", headers=headers)
    assert len(listed.json()["businesses"]) == 2


def test_duplicate_membership_for_same_business_is_prevented(verified_user):
    db = SessionLocal()
    business = Business(name="Constraint Store")
    db.add(business)
    db.flush()
    db.add(Membership(business_id=business.id, user_id=verified_user.id))
    db.commit()
    try:
        db.add(Membership(business_id=business.id, user_id=verified_user.id))
        with pytest.raises(Exception):
            db.commit()
        db.rollback()
    finally:
        db.close()
