from __future__ import annotations

import json
from datetime import datetime
from typing import Any

from django.db import DatabaseError, connections, transaction
from django.utils import timezone
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from apps.users.models import Business, User, UserPermissionsProfile
from config.reporting_utils import _admin_role, _legacy_profile_for_user
from django.contrib.auth.hashers import make_password


def _legacy(request) -> dict[str, Any] | None:
    return _legacy_profile_for_user(request.user)


def _require_admin(request) -> tuple[dict[str, Any] | None, Response | None]:
    legacy = _legacy(request)
    if not legacy:
        return None, Response({"success": False, "message": "No business profile for user."}, status=403)
    if not _admin_role(legacy.get("role")):
        return legacy, Response({"success": False, "message": "Admin access required."}, status=403)
    return legacy, None


def _safe_json_loads(s: Any, default):
    if s is None:
        return default
    if isinstance(s, (dict, list)):
        return s
    try:
        return json.loads(str(s))
    except Exception:
        return default


def _default_permissions_for_role(role: Any) -> list[dict[str, Any]]:
    role_norm = str(role or "").strip().lower()
    modules = ["Dashboard", "Reports", "Analytics", "Knowledge Base", "Bot Management", "User Management"]
    if role_norm in {"admin", "superadmin", "super_admin", "owner"}:
        return [
            {"module": m, "permissions": {"view": True, "create": True, "edit": True, "delete": True}}
            for m in modules
        ]
    # Agent default: view only for most modules, no user management
    perms = []
    for m in modules:
        if m == "User Management":
            perms.append({"module": m, "permissions": {"view": False, "create": False, "edit": False, "delete": False}})
        else:
            perms.append({"module": m, "permissions": {"view": True, "create": False, "edit": False, "delete": False}})
    return perms


def _user_to_dict(u: User) -> dict[str, Any]:
    # Frontend expects `id` string and `lastLogin` (camelCase) but it tolerates missing.
    # We'll provide ISO strings; frontend converts to Date where needed.
    prof = getattr(u, "permissions_profile", None)
    permissions = _safe_json_loads(getattr(prof, "permissions_json", None), [])
    if not permissions:
        permissions = _default_permissions_for_role(u.role)

    return {
        "id": str(u.user_id),
        "name": u.name or u.username or "",
        "email": u.email or "",
        "role": (u.role or "").strip().lower() or "agent",
        "agentId": u.agent_id,
        "permissions": permissions,
        "reportsTo": None,
        "lastLogin": (u.updated_at.isoformat() if u.updated_at else None),
        "phone": None,
        "department": None,
        "status": (u.status or "active").strip().lower() if u.status is not None else "active",
        "business_id": getattr(u.business, "business_id", None),
    }


def _q_ident(name: str) -> str:
    # Minimal MySQL identifier quoting.
    return f"`{name.replace('`', '``')}`"


def _audit_table_name(business_id: int) -> str:
    return f"{int(business_id)}__audit_logs"


def _ensure_audit_table_exists(*, cur, table: str) -> None:
    """
    Ensure per-business audit log table exists in the cluster DB.
    Table name must already be sanitized/constructed by server-side code.
    """
    ddl = f"""
    CREATE TABLE IF NOT EXISTS {_q_ident(table)} (
      audit_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      user_id VARCHAR(64) NOT NULL DEFAULT '',
      user_name VARCHAR(255) NOT NULL DEFAULT '',
      user_role VARCHAR(64) NOT NULL DEFAULT '',
      action VARCHAR(64) NOT NULL,
      module VARCHAR(64) NOT NULL,
      resource_type VARCHAR(128) NOT NULL DEFAULT '',
      resource_name VARCHAR(255) NULL,
      description LONGTEXT NULL,
      severity VARCHAR(16) NOT NULL DEFAULT 'low',
      status VARCHAR(16) NOT NULL DEFAULT 'success',
      ip_address VARCHAR(64) NULL,
      user_agent LONGTEXT NULL,
      PRIMARY KEY (audit_id),
      KEY idx_timestamp (timestamp),
      KEY idx_module (module),
      KEY idx_action (action)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
    """
    cur.execute(ddl)


def _log(
    *,
    request,
    legacy: dict[str, Any] | None,
    action: str,
    module: str,
    resource_type: str,
    resource_name: str | None,
    description: str,
    severity: str = "low",
    status: str = "success",
):
    user_name = ""
    user_role = ""
    user_id = ""
    if legacy:
        user_id = str(legacy.get("agent_id") or "") or str(getattr(request.user, "id", "") or "")
        user_role = str(legacy.get("role") or "")
    user_name = (getattr(request.user, "username", None) or getattr(request.user, "email", None) or "").strip()

    business_id = None
    if legacy:
        business_id = legacy.get("business_id")
    try:
        bid = int(business_id) if business_id is not None else None
    except Exception:
        bid = None
    if not bid or bid <= 0:
        # Without a business_id we cannot determine the per-business audit table.
        return

    table = _audit_table_name(bid)
    try:
        with connections["cluster"].cursor() as cur:
            _ensure_audit_table_exists(cur=cur, table=table)
            cur.execute(
                f"""
                INSERT INTO {_q_ident(table)}
                  (user_id, user_name, user_role, action, module, resource_type, resource_name,
                   description, severity, status, ip_address, user_agent)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                """,
                [
                    user_id,
                    user_name,
                    user_role,
                    action,
                    module,
                    resource_type,
                    resource_name,
                    description,
                    severity,
                    status,
                    request.META.get("REMOTE_ADDR"),
                    request.META.get("HTTP_USER_AGENT"),
                ],
            )
    except DatabaseError:
        # Audit logs should never break the primary request path.
        return


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def users_collection(request):
    legacy, err = _require_admin(request)
    if err:
        return err

    if request.method == "GET":
        bid = legacy.get("business_id")
        qs = User.objects.all().select_related("business")
        if bid is not None:
            qs = qs.filter(business_id=int(bid))
        users = list(qs.order_by("-user_id")[:500])
        data = [_user_to_dict(u) for u in users]
        return Response(data)

    # POST create
    payload = request.data if isinstance(request.data, dict) else {}
    username = str(payload.get("username") or "").strip()
    email = str(payload.get("email") or "").strip()
    name = str(payload.get("name") or payload.get("full_name") or "").strip()
    role = str(payload.get("role") or "agent").strip().lower()
    status_val = str(payload.get("status") or "pending").strip().lower()
    password = str(payload.get("password") or "").strip()
    agent_id = payload.get("agent_id") or payload.get("agentId")

    if not username and email:
        username = email.split("@")[0]
    if not username:
        return Response({"success": False, "message": "username is required."}, status=400)
    if not password:
        return Response({"success": False, "message": "password is required."}, status=400)

    bid = legacy.get("business_id")
    if not bid:
        return Response({"success": False, "message": "Business not found."}, status=400)
    business = Business.objects.filter(pk=int(bid)).first()
    if not business:
        return Response({"success": False, "message": "Business record not found."}, status=404)

    with transaction.atomic():
        u = User.objects.create(
            username=username,
            name=name,
            email=email,
            password=make_password(password),
            role=role,
            status=status_val,
            business=business,
            agent_id=str(agent_id) if agent_id not in (None, "") else None,
        )
        # Optional initial permissions
        perms = payload.get("permissions")
        if perms is not None:
            UserPermissionsProfile.objects.update_or_create(
                user=u,
                defaults={"permissions_json": json.dumps(perms)},
            )
        _log(
            request=request,
            legacy=legacy,
            action="create",
            module="User Management",
            resource_type="User",
            resource_name=(name or username),
            description=f"Created user {name or username}",
            severity="medium",
        )

    return Response({"success": True, "data": _user_to_dict(u)})


@api_view(["GET", "PUT", "DELETE"])
@permission_classes([IsAuthenticated])
def user_detail(request, user_id: str):
    legacy, err = _require_admin(request)
    if err:
        return err

    try:
        uid = int(user_id)
    except Exception:
        return Response({"success": False, "message": "Invalid user id."}, status=400)

    u = User.objects.filter(pk=uid).select_related("business").first()
    if not u:
        return Response({"success": False, "message": "User not found."}, status=404)

    # restrict to same business
    bid = legacy.get("business_id")
    if bid is not None and int(bid) != int(getattr(u.business, "business_id", 0)):
        return Response({"success": False, "message": "You cannot access this user."}, status=403)

    if request.method == "GET":
        return Response({"success": True, "data": _user_to_dict(u)})

    if request.method == "DELETE":
        name = u.name or u.username
        u.delete()
        _log(
            request=request,
            legacy=legacy,
            action="delete",
            module="User Management",
            resource_type="User",
            resource_name=name,
            description=f"Deleted user {name}",
            severity="high",
        )
        return Response({"success": True, "message": "User deleted."})

    payload = request.data if isinstance(request.data, dict) else {}
    changed = []

    for key, attr in [
        ("username", "username"),
        ("email", "email"),
        ("name", "name"),
        ("role", "role"),
        ("status", "status"),
        ("agent_id", "agent_id"),
        ("agentId", "agent_id"),
    ]:
        if key in payload and getattr(u, attr) != payload.get(key):
            setattr(u, attr, payload.get(key) or "")
            changed.append(attr)

    if "password" in payload and str(payload.get("password") or "").strip():
        u.password = make_password(str(payload.get("password")))
        changed.append("password")

    u.updated_at = timezone.now()
    u.save()

    _log(
        request=request,
        legacy=legacy,
        action="update",
        module="User Management",
        resource_type="User",
        resource_name=(u.name or u.username),
        description=f"Updated user {u.name or u.username}: {', '.join(changed) if changed else 'no field changes'}",
        severity="medium",
    )

    return Response({"success": True, "data": _user_to_dict(u)})


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def user_toggle_status(request, user_id: str):
    legacy, err = _require_admin(request)
    if err:
        return err

    try:
        uid = int(user_id)
    except Exception:
        return Response({"success": False, "message": "Invalid user id."}, status=400)

    u = User.objects.filter(pk=uid).select_related("business").first()
    if not u:
        return Response({"success": False, "message": "User not found."}, status=404)

    bid = legacy.get("business_id")
    if bid is not None and int(bid) != int(getattr(u.business, "business_id", 0)):
        return Response({"success": False, "message": "You cannot access this user."}, status=403)

    cur = (u.status or "active").strip().lower()
    u.status = "inactive" if cur == "active" else "active"
    u.updated_at = timezone.now()
    u.save(update_fields=["status", "updated_at"])

    _log(
        request=request,
        legacy=legacy,
        action="update",
        module="User Management",
        resource_type="User",
        resource_name=(u.name or u.username),
        description=f"Toggled user status to {u.status}",
        severity="low",
    )

    return Response({"success": True, "status": u.status})


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def user_resend_invitation(request, user_id: str):
    legacy, err = _require_admin(request)
    if err:
        return err

    # No mail integration in this backend; return success for UI.
    _log(
        request=request,
        legacy=legacy,
        action="update",
        module="User Management",
        resource_type="User",
        resource_name=user_id,
        description="Resent invitation (stub).",
        severity="low",
    )
    return Response({"success": True, "message": "Invitation resent."})


@api_view(["PUT"])
@permission_classes([IsAuthenticated])
def user_permissions_update(request, user_id: str):
    legacy, err = _require_admin(request)
    if err:
        return err

    try:
        uid = int(user_id)
    except Exception:
        return Response({"success": False, "message": "Invalid user id."}, status=400)

    u = User.objects.filter(pk=uid).select_related("business").first()
    if not u:
        return Response({"success": False, "message": "User not found."}, status=404)

    bid = legacy.get("business_id")
    if bid is not None and int(bid) != int(getattr(u.business, "business_id", 0)):
        return Response({"success": False, "message": "You cannot access this user."}, status=403)

    payload = request.data if isinstance(request.data, dict) else {}
    permissions = payload.get("permissions", [])
    try:
        permissions_json = json.dumps(permissions)
    except Exception:
        return Response({"success": False, "message": "Invalid permissions payload."}, status=400)

    UserPermissionsProfile.objects.update_or_create(
        user=u,
        defaults={"permissions_json": permissions_json},
    )

    _log(
        request=request,
        legacy=legacy,
        action="update",
        module="Permissions",
        resource_type="User Permissions",
        resource_name=(u.name or u.username),
        description=f"Updated permissions for {u.name or u.username}",
        severity="high",
    )

    return Response({"success": True, "user": _user_to_dict(u)})


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def audit_logs(request):
    legacy, err = _require_admin(request)
    if err:
        return err

    q = request.query_params
    start_date = (q.get("start_date") or "").strip()
    end_date = (q.get("end_date") or "").strip()
    module = (q.get("module") or "").strip()
    action = (q.get("action") or "").strip()
    severity = (q.get("severity") or "").strip()
    status = (q.get("status") or "").strip()
    search = (q.get("search") or q.get("searchTerm") or "").strip().lower()

    business_id = legacy.get("business_id") if legacy else None
    try:
        bid = int(business_id) if business_id is not None else None
    except Exception:
        bid = None
    if not bid or bid <= 0:
        return Response({"logs": []})

    table = _audit_table_name(bid)

    # Date filters accept YYYY-MM-DD
    def _parse_date(s: str) -> datetime | None:
        try:
            return datetime.strptime(s, "%Y-%m-%d")
        except Exception:
            return None

    sd = _parse_date(start_date) if start_date else None
    ed = _parse_date(end_date) if end_date else None
    where = []
    params: list[Any] = []

    if sd:
        where.append("timestamp >= %s")
        params.append(sd)
    if ed:
        where.append("timestamp < %s")
        params.append(ed.replace(hour=23, minute=59, second=59))

    if module and module.lower() != "all":
        where.append("module = %s")
        params.append(module)
    if action and action.lower() != "all":
        where.append("action = %s")
        params.append(action)
    if severity and severity.lower() != "all":
        where.append("severity = %s")
        params.append(severity)
    if status and status.lower() != "all":
        where.append("status = %s")
        params.append(status)

    if search:
        like = f"%{search}%"
        where.append(
            "("
            "LOWER(COALESCE(user_name,'')) LIKE %s OR "
            "LOWER(COALESCE(action,'')) LIKE %s OR "
            "LOWER(COALESCE(module,'')) LIKE %s OR "
            "LOWER(COALESCE(resource_type,'')) LIKE %s OR "
            "LOWER(COALESCE(description,'')) LIKE %s"
            ")"
        )
        params.extend([like, like, like, like, like])

    sql = f"""
        SELECT
          audit_id, timestamp, user_name, user_role, action, module,
          resource_type, resource_name, severity, status, description, ip_address, user_agent
        FROM {_q_ident(table)}
        {("WHERE " + " AND ".join(where)) if where else ""}
        ORDER BY timestamp DESC
        LIMIT 500
    """

    try:
        with connections["cluster"].cursor() as cur:
            _ensure_audit_table_exists(cur=cur, table=table)
            cur.execute(sql, params)
            rows = cur.fetchall() or []
            colnames = [c[0] for c in cur.description] if cur.description else []
    except DatabaseError:
        return Response({"logs": []})

    out = []
    for row in rows:
        d = dict(zip(colnames, row))
        ts = d.get("timestamp")
        out.append(
            {
                "id": str(d.get("audit_id")),
                "timestamp": (ts.isoformat() if hasattr(ts, "isoformat") else (str(ts) if ts else None)),
                "user_name": d.get("user_name") or "System",
                "user_role": d.get("user_role") or "system",
                "action": d.get("action"),
                "module": d.get("module"),
                "resource_type": d.get("resource_type"),
                "resource_name": d.get("resource_name"),
                "severity": d.get("severity"),
                "status": d.get("status"),
                "description": d.get("description"),
                "ip_address": d.get("ip_address"),
                "user_agent": d.get("user_agent"),
            }
        )

    return Response({"logs": out})

