from __future__ import annotations
from datetime import datetime, timezone, timedelta
from typing import Optional
from sqlalchemy import select, func
from sqlalchemy.orm import Session

from src.apps.admin.models.admin_audit_log import AdminAuditLog
from src.apps.admin.crud import get_merchant_by_id
from src.apps.auth.utils.jwt import jwt_manager
from src.apps.auth.models.auth_session import AuthSession


def create_merchant_with_owner(
    db: Session,
    merchant,
    owner_user,
) -> None:
    """
    PRD-008: Bootstrap RBAC roles for a newly created merchant.

    Call this after creating MerchantUsers owner row and flushing::

        mu = MerchantUsers(user_id=owner_user.id, merchant_id=merchant.id, is_owner=True)
        db.add(mu)
        db.flush()
        create_merchant_with_owner(db, merchant, owner_user)

    Idempotent — safe to call multiple times.
    """
    from src.apps.role_permissions.services import bootstrap_merchant_roles

    bootstrap_merchant_roles(db, merchant_id=merchant.id, owner_user_id=owner_user.id)


def get_dashboard_stats(db: Session) -> dict:
    from src.apps.merchants.models.merchant import Merchant
    from src.apps.transactions.models.transactions import Transactions

    total = db.execute(
        select(func.count(Merchant.id)).where(Merchant.deleted_at.is_(None))
    ).scalar_one()

    now = datetime.now(timezone.utc)
    start_of_today = now.replace(hour=0, minute=0, second=0, microsecond=0)
    start_of_week = now - timedelta(days=now.weekday())
    start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
    start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

    txn_today = db.execute(
        select(func.count(Transactions.id)).where(Transactions.ocurred_at >= start_of_today)
    ).scalar_one()
    txn_week = db.execute(
        select(func.count(Transactions.id)).where(Transactions.ocurred_at >= start_of_week)
    ).scalar_one()
    txn_month = db.execute(
        select(func.count(Transactions.id)).where(Transactions.ocurred_at >= start_of_month)
    ).scalar_one()

    vol_today = db.execute(
        select(func.coalesce(func.sum(Transactions.txn_amount), 0)).where(
            Transactions.ocurred_at >= start_of_today
        )
    ).scalar_one()
    vol_week = db.execute(
        select(func.coalesce(func.sum(Transactions.txn_amount), 0)).where(
            Transactions.ocurred_at >= start_of_week
        )
    ).scalar_one()
    vol_month = db.execute(
        select(func.coalesce(func.sum(Transactions.txn_amount), 0)).where(
            Transactions.ocurred_at >= start_of_month
        )
    ).scalar_one()

    new_this_month = db.execute(
        select(func.count(Merchant.id)).where(
            Merchant.deleted_at.is_(None),
            Merchant.created_at >= start_of_month,
        )
    ).scalar_one()

    # Merchant counts by status — use hybrid property logic directly in Python
    all_merchants = db.execute(
        select(Merchant).where(Merchant.deleted_at.is_(None))
    ).scalars().all()

    status_counts: dict = {}
    for m in all_merchants:
        s = m.status or "unknown"
        status_counts[s] = status_counts.get(s, 0) + 1

    return {
        "total_merchants": total,
        "merchants_by_status": status_counts,
        "transaction_counts": {"today": txn_today, "this_week": txn_week, "this_month": txn_month},
        "transaction_volume": {
            "today": float(vol_today),
            "this_week": float(vol_week),
            "this_month": float(vol_month),
        },
        "new_merchants_this_month": new_this_month,
    }


def create_impersonation_token(
    db: Session,
    merchant_id: int,
    admin_user_id: int,
    ip_address: Optional[str] = None,
    merchant_name: Optional[str] = None,
) -> str:
    from src.apps.users.models.user import User

    admin_user = db.execute(select(User).where(User.id == admin_user_id)).scalar_one_or_none()
    if not admin_user:
        raise ValueError("Admin user not found")

    # Revoke any existing active impersonation sessions for this (admin, merchant) pair
    existing = db.execute(
        select(AuthSession).where(
            AuthSession.user_id == admin_user_id,
            AuthSession.impersonated_merchant_id == merchant_id,
            AuthSession.is_active == True,
            AuthSession.is_revoked == False,
        )
    ).scalars().all()
    for s in existing:
        s.revoke()

    expires_delta = timedelta(hours=4)
    token_data = {
        "user": {"user_id": admin_user_id, "email": admin_user.email},
        "impersonated_merchant_id": merchant_id,
        "token_type": "impersonation",
        "merchant_name": merchant_name or f"Merchant #{merchant_id}",
        "admin_email": admin_user.email,
    }
    access_token = jwt_manager.create_access_token(token_data, expires_delta=expires_delta)

    expires_at = datetime.now(timezone.utc) + expires_delta

    session = AuthSession(
        user_id=admin_user_id,
        access_token=access_token,
        refresh_token=None,
        access_token_expires_at=expires_at,
        is_active=True,
        is_revoked=False,
        ip_address=ip_address,
        impersonated_merchant_id=merchant_id,
    )
    db.add(session)
    db.flush()

    AdminAuditLog.log(
        db,
        admin_user_id=admin_user_id,
        action="merchant.impersonate",
        target_type="merchant",
        target_id=merchant_id,
        ip_address=ip_address,
    )
    db.commit()
    return access_token


def revoke_impersonation_session(
    db: Session,
    merchant_id: int,
    admin_user_id: int,
    ip_address: Optional[str] = None,
) -> bool:
    sessions = db.execute(
        select(AuthSession).where(
            AuthSession.user_id == admin_user_id,
            AuthSession.impersonated_merchant_id == merchant_id,
            AuthSession.is_active == True,
            AuthSession.is_revoked == False,
        )
    ).scalars().all()
    if sessions:
        for session in sessions:
            session.revoke()
        AdminAuditLog.log(
            db,
            admin_user_id=admin_user_id,
            action="merchant.impersonate.end",
            target_type="merchant",
            target_id=merchant_id,
            ip_address=ip_address,
        )
        db.commit()
        return True
    return False
