import uuid
from datetime import datetime, timedelta, timezone

from fastapi.testclient import TestClient
from sqlalchemy import delete

from app.core.security import create_access_token, hash_password
from app.db.session import SessionLocal
from app.main import app
from app.models.audit import AuditLog
from app.models.branch import Branch
from app.models.business import Business
from app.models.membership import Membership, MembershipBranch
from app.models.user import User
from app.services.audit import record_audit


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


def setup():
    db = SessionLocal(); marker = uuid.uuid4().hex
    ceo = User(full_name="CEO", email=f"audit-ceo-{marker}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    accountant = User(full_name="Accountant", email=f"audit-accountant-{marker}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    cashier = User(full_name="Cashier", email=f"audit-cashier-{marker}@example.com", password_hash=hash_password("TestPassword123!"), is_email_verified=True)
    business = Business(name="Audit Store")
    other_business = Business(name="Other Audit Store")
    db.add_all([ceo, accountant, cashier, business, other_business]); db.flush()
    main = Branch(business_id=business.id, name="Main", is_main=True)
    other = Branch(business_id=business.id, name="Other")
    other_branch = Branch(business_id=other_business.id, name="Other Main", is_main=True)
    ceo_member = Membership(business_id=business.id, user_id=ceo.id, role="CEO", all_branches=True)
    accountant_member = Membership(business_id=business.id, user_id=accountant.id, role="ACCOUNTANT", all_branches=False)
    cashier_member = Membership(business_id=business.id, user_id=cashier.id, role="CASHIER", all_branches=True)
    db.add_all([main, other, other_branch, ceo_member, accountant_member, cashier_member]); db.flush()
    db.add(MembershipBranch(membership_id=accountant_member.id, branch_id=main.id)); db.commit()
    return db, business, other_business, main, other, ceo, accountant, cashier, accountant_member


def cleanup(db, businesses, users):
    db.execute(delete(AuditLog).where(AuditLog.business_id.in_([business.id for business in businesses])))
    db.execute(delete(MembershipBranch).where(MembershipBranch.membership_id.in_(db.query(Membership.id).filter(Membership.business_id.in_([business.id for business in businesses])))))
    db.execute(delete(Membership).where(Membership.business_id.in_([business.id for business in businesses])))
    db.execute(delete(Branch).where(Branch.business_id.in_([business.id for business in businesses])))
    db.execute(delete(Business).where(Business.id.in_([business.id for business in businesses])))
    db.execute(delete(User).where(User.id.in_([user.id for user in users])))
    db.commit(); db.close()


def add_log(db, business, branch, actor, action="STOCK_ADJUSTED", entity_type="product", entity_id=None, created_at=None):
    item = record_audit(db, business.id, action, entity_type, entity_id or uuid.uuid4(), actor.id, branch.id if branch else None, {"reference": "safe-reference", "count": 2})
    if created_at:
        item.created_at = created_at
    db.commit(); return item


def test_real_staff_mutation_creates_audit_row_with_safe_metadata():
    db, business, other_business, main, other, ceo, accountant, cashier, accountant_member = setup()
    try:
        with TestClient(app) as client:
            response = client.patch(f"/api/businesses/{business.id}/staff/{accountant_member.id}/role", json={"role": "MANAGER"}, headers=headers(ceo))
            assert response.status_code == 200
        row = db.query(AuditLog).filter_by(business_id=business.id, action="STAFF_ROLE_CHANGED", entity_id=accountant_member.id).one()
        assert row.actor_user_id == ceo.id and row.branch_id is None
        assert row.entity_type == "membership"
        assert row.details == {"staff_member_id": str(accountant_member.id), "previous_role": "ACCOUNTANT", "new_role": "MANAGER"}
    finally:
        cleanup(db, [business, other_business], [ceo, accountant, cashier])


def test_audit_sanitizes_nested_secrets_and_preserves_safe_metadata():
    db, business, other_business, main, other, ceo, accountant, cashier, _ = setup()
    try:
        row = record_audit(db, business.id, "TEST", "test", actor_user_id=ceo.id, details={"reference": "visible", "nested": {"password": "hidden", "items": [{"access_token": "hidden"}, {"api_key": "hidden", "value": 7}]}, "reset_token": "hidden", "verification_code": "hidden"})
        db.commit(); db.refresh(row)
        assert row.details["reference"] == "visible" and row.details["nested"]["items"][1]["value"] == 7
        assert row.details["nested"]["password"] == "[REDACTED]"
        assert row.details["nested"]["items"][0]["access_token"] == "[REDACTED]"
        assert row.details["nested"]["items"][1]["api_key"] == "[REDACTED]"
        assert row.details["reset_token"] == "[REDACTED]" and row.details["verification_code"] == "[REDACTED]"
    finally:
        cleanup(db, [business, other_business], [ceo, accountant, cashier])


def test_audit_list_detail_filters_pagination_and_visibility():
    db, business, other_business, main, other, ceo, accountant, cashier, _ = setup()
    try:
        entity_id = uuid.uuid4()
        first = add_log(db, business, main, ceo, "STOCK_ADJUSTED", "product", entity_id, datetime.now(timezone.utc) - timedelta(days=2))
        second = add_log(db, business, other, accountant, "PRODUCT_CREATED", "product", uuid.uuid4())
        add_log(db, other_business, None, cashier, "STOCK_ADJUSTED", "product")
        with TestClient(app) as client:
            base = f"/api/businesses/{business.id}/audit"
            listed = client.get(base, headers=headers(ceo)); assert listed.status_code == 200 and len(listed.json()) == 2
            assert client.get(base, params={"action": "STOCK_ADJUSTED", "entity_type": "product", "entity_id": str(entity_id), "actor_user_id": str(ceo.id), "branch_id": str(main.id), "start_date": (datetime.now(timezone.utc)-timedelta(days=3)).date().isoformat(), "end_date": datetime.now(timezone.utc).date().isoformat()}, headers=headers(ceo)).json()[0]["id"] == str(first.id)
            page = client.get(base, params={"offset": 1, "limit": 1}, headers=headers(ceo)); assert page.status_code == 200 and len(page.json()) == 1
            detail = client.get(f"{base}/{first.id}", headers=headers(ceo)); assert detail.status_code == 200 and detail.json()["id"] == str(first.id)
            assert client.get(f"/api/businesses/{other_business.id}/audit", headers=headers(ceo)).status_code == 404
            limited = client.get(base, headers=headers(accountant)); assert limited.status_code == 200 and [item["id"] for item in limited.json()] == [str(first.id)]
            assert client.get(f"{base}/{second.id}", headers=headers(accountant)).status_code == 404
            assert client.get(base, headers=headers(cashier)).status_code == 403
            assert client.get(f"{base}/{first.id}", headers=headers(cashier)).status_code == 403
            assert client.patch(f"{base}/{first.id}", headers=headers(ceo)).status_code == 405
            assert client.delete(f"{base}/{first.id}", headers=headers(ceo)).status_code == 405
    finally:
        cleanup(db, [business, other_business], [ceo, accountant, cashier])
