from datetime import datetime, timedelta, timezone
from decimal import Decimal
import uuid

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.models.business import Business
from app.models.branch import Branch
from app.models.membership import Membership, MembershipBranch
from app.models.sale import Sale, SalePayment
from app.models.user import User
from app.main import app


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


@pytest.fixture
def verified_user():
    db = SessionLocal(); user = User(email=f"day-close-{uuid.uuid4()}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    db.add(user); db.commit(); db.refresh(user)
    try:
        yield user
    finally:
        business_ids = list(db.scalars(select(Membership.business_id).where(Membership.user_id == user.id)))
        if business_ids:
            db.execute(delete(SalePayment).where(SalePayment.business_id.in_(business_ids)))
            db.execute(delete(Sale).where(Sale.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()


def headers(user): return {"Authorization": f"Bearer {create_access_token(str(user.id))}"}


def workspace(client, user, name="Close Store"):
    response = client.post("/api/businesses/onboarding", json={"name": name, "phone": "", "email": "", "address": "", "city": "", "state": "", "country": "Nigeria", "currency": "NGN", "default_language": "en", "main_branch_name": "Main"}, headers=headers(user))
    assert response.status_code == 201
    return response.json()


def payment(db, business_id, branch_id, user_id, amount, method, paid_at):
    sale = Sale(business_id=business_id, branch_id=branch_id, receipt_number=f"R-{uuid.uuid4()}", sale_datetime=paid_at, lifecycle_status="COMPLETED", payment_status="PAID", subtotal=amount, discount=0, tax=0, total=amount, amount_paid=amount, balance=0, created_by=user_id)
    db.add(sale); db.flush()
    db.add(SalePayment(business_id=business_id, sale_id=sale.id, amount=amount, payment_method=method, recorded_by=user_id, paid_at=paid_at))


def test_day_close_counts_only_completed_cash_and_prevents_overlap(client, verified_user):
    data = workspace(client, verified_user); business_id = data["business"]["id"]; branch_id = data["accessible_branches"][0]["id"]
    start = datetime.now(timezone.utc).replace(hour=8, minute=0, second=0, microsecond=0); end = start + timedelta(hours=10)
    db = SessionLocal()
    try:
        payment(db, business_id, branch_id, verified_user.id, Decimal("100.00"), "CASH", start + timedelta(hours=1))
        payment(db, business_id, branch_id, verified_user.id, Decimal("50.00"), "POS", start + timedelta(hours=1))
        payment(db, business_id, branch_id, verified_user.id, Decimal("40.00"), "TRANSFER", start + timedelta(hours=1))
        payment(db, business_id, branch_id, verified_user.id, Decimal("30.00"), "OTHER", start + timedelta(hours=1))
        # An unpaid credit sale has no SalePayment and must not contribute cash.
        db.add(Sale(business_id=business_id, branch_id=branch_id, receipt_number=f"CREDIT-{uuid.uuid4()}", sale_datetime=start + timedelta(hours=2), lifecycle_status="COMPLETED", payment_status="UNPAID", subtotal=Decimal("75.00"), discount=0, tax=0, total=Decimal("75.00"), amount_paid=0, balance=Decimal("75.00"), created_by=verified_user.id))
        db.commit()
        payload = {"branch_id": branch_id, "period_start": start.isoformat(), "period_end": end.isoformat()}
        preview = client.post(f"/api/businesses/{business_id}/day-closes/preview", json=payload, headers=headers(verified_user))
        assert preview.status_code == 200 and preview.json()["expected_cash"] == "100.00"
        created = client.post(f"/api/businesses/{business_id}/day-closes", json={**payload, "counted_cash": "95.00"}, headers=headers(verified_user))
        assert created.status_code == 201
        assert created.json()["expected_cash"] == "100.00" and created.json()["difference"] == "-5.00"
        duplicate = client.post(f"/api/businesses/{business_id}/day-closes", json={**payload, "counted_cash": "100.00"}, headers=headers(verified_user))
        assert duplicate.status_code == 409
        history = client.get(f"/api/businesses/{business_id}/day-closes", headers=headers(verified_user))
        assert history.status_code == 200 and len(history.json()) == 1
    finally: db.close()


def test_day_close_enforces_business_branch_and_role_isolation(client, verified_user):
    first = workspace(client, verified_user); business_id = first["business"]["id"]; branch_id = first["accessible_branches"][0]["id"]
    start = datetime.now(timezone.utc).replace(hour=8, minute=0, second=0, microsecond=0); end = start + timedelta(hours=8)
    payload = {"branch_id": branch_id, "period_start": start.isoformat(), "period_end": end.isoformat(), "counted_cash": "0"}
    db = SessionLocal(); cashier = User(email=f"cashier-{uuid.uuid4()}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    outsider = User(email=f"outsider-{uuid.uuid4()}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    db.add_all([cashier, outsider]); db.flush()
    membership = Membership(business_id=uuid.UUID(business_id), user_id=cashier.id, role="CASHIER", all_branches=False)
    other_branch = Branch(business_id=uuid.UUID(business_id), name="Restricted")
    db.add_all([membership, other_branch]); db.flush(); db.add(MembershipBranch(membership_id=membership.id, branch_id=uuid.UUID(branch_id))); db.commit()
    accountant = User(email=f"accountant-{uuid.uuid4()}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    db.add(accountant); db.flush(); accountant_membership = Membership(business_id=uuid.UUID(business_id), user_id=accountant.id, role="ACCOUNTANT", all_branches=False)
    db.add(accountant_membership); db.flush(); db.add(MembershipBranch(membership_id=accountant_membership.id, branch_id=uuid.UUID(branch_id))); db.commit()
    try:
        assert client.post(f"/api/businesses/{business_id}/day-closes", json=payload, headers=headers(cashier)).status_code == 403
        assert client.get(f"/api/businesses/{business_id}/day-closes", headers=headers(outsider)).status_code == 404
        restricted = client.post(f"/api/businesses/{business_id}/day-closes/preview", json={**payload, "branch_id": str(other_branch.id)}, headers=headers(accountant))
        assert restricted.status_code == 403
    finally:
        db.execute(delete(MembershipBranch).where(MembershipBranch.membership_id.in_([membership.id, accountant_membership.id])))
        db.execute(delete(Membership).where(Membership.user_id.in_([cashier.id, accountant.id]))); db.execute(delete(User).where(User.id.in_([cashier.id, accountant.id, outsider.id]))); db.commit(); db.close()
