from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, func
from typing import List, Optional, Dict, Any
from uuid import uuid4
from datetime import datetime

from app.core.database import get_db
from app.core.security import require_any, get_password_hash, verify_password, get_current_user
from app.core.dependencies import get_current_company_id
from app.models.user import User
from app.models.branch import Branch
from app.models.role import Role
from app.schemas.auth import UserCreate, UserResponse, UserUpdate
from app.schemas.whatsapp import SendLoginWhatsAppRequest, SendLoginWhatsAppResponse
from app.core.security import log_user_action
from pydantic import BaseModel
from app.utils.logger import get_logger, log_exception, log_error_with_context

logger = get_logger(__name__)
router = APIRouter()

# Define role hierarchy and branch access rules
GLOBAL_ROLES = {"super_admin", "admin", "accountant"}  # Can access any branch
BRANCH_SPECIFIC_ROLES = {"manager", "cashier", "staff"}  # Must be assigned to specific branches
USER_MANAGEMENT_ROLES = {"super_admin", "admin", "manager", "accountant"}


def _actor_role(user: User) -> str:
    return (user.role or "").lower()


def _assert_can_manage_users(actor: User) -> None:
    if _actor_role(actor) not in USER_MANAGEMENT_ROLES:
        raise HTTPException(status_code=403, detail="Not authorized to manage users")


def _assert_can_transfer_user(
    actor: User,
    target: User,
    branch: Branch,
    company_id: Optional[str],
) -> None:
    _assert_can_manage_users(actor)
    actor_role = _actor_role(actor)
    target_role = _actor_role(target)

    actor_company = actor.company_id or company_id
    if target.company_id and actor_company and target.company_id != actor_company:
        raise HTTPException(status_code=403, detail="User is not in your company")
    if branch.company_id != (target.company_id or actor_company):
        raise HTTPException(status_code=400, detail="Branch must belong to the same company")

    if actor_role in ("manager", "accountant") and target_role in ("super_admin", "admin"):
        raise HTTPException(status_code=403, detail="Cannot transfer administrator accounts")
    if actor_role == "manager" and target_role in ("manager", "accountant"):
        raise HTTPException(
            status_code=403,
            detail="Managers can transfer cashiers and staff only",
        )

class UserDetailResponse(BaseModel):
    id: str
    username: str
    email: Optional[str]
    first_name: Optional[str]
    last_name: Optional[str]
    phone: Optional[str]
    address: Optional[str]
    role: str
    branch_id: Optional[str]
    branch_name: Optional[str]
    branch_code: Optional[str]
    active: bool
    created_at: Optional[datetime]
    updated_at: Optional[datetime]
    last_login: Optional[datetime]
    login_count: Optional[int]
    notes: Optional[str]

class BranchAssignmentRequest(BaseModel):
    branch_id: str

class UserCreateRequest(BaseModel):
    username: str
    email: str
    password: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None
    role: str
    branch_id: Optional[str] = None
    notes: Optional[str] = None
    generate_temp_password: bool = False
    send_whatsapp_login: bool = False
    prefer_cloud_login: bool = False
    whatsapp_phone: Optional[str] = None


class UserCreateResponse(UserResponse):
    temporary_password: Optional[str] = None
    whatsapp_sent: bool = False
    whatsapp_message: Optional[str] = None

class UserUpdateRequest(BaseModel):
    email: Optional[str] = None
    password: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None
    role: Optional[str] = None
    branch_id: Optional[str] = None
    notes: Optional[str] = None
    active: Optional[bool] = None


@router.get("/", response_model=List[UserResponse])
async def get_users(
    db: Session = Depends(get_db),
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=500),
    role: Optional[str] = Query(None, description="Filter by role"),
    active: Optional[bool] = Query(None, description="Filter by active status"),
    branch_id: Optional[str] = Query(None, description="Filter by branch id"),
    search: Optional[str] = Query(None, description="Search username, email, first/last name"),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get users with filtering & pagination and branch access rules."""
    query = db.query(User)

    if company_id:
        query = query.filter(User.company_id == company_id)

    # Filter by active status
    if active is not None:
        query = query.filter(User.active == active)
    else:
        query = query.filter(User.active == True)

    if role:
        query = query.filter(func.lower(User.role) == role.lower())
    if branch_id:
        query = query.filter(User.branch_id == branch_id)
    if search:
        like = f"%{search.lower()}%"
        query = query.filter(or_(
            func.lower(User.username).like(like),
            func.lower(User.email).like(like),
            func.lower(User.first_name).like(like),
            func.lower(User.last_name).like(like)
        ))

    total = query.count()
    users = query.offset(skip).limit(limit).all()

    return [
        UserResponse(
            id=user.id,
            username=user.username,
            email=user.email,
            first_name=user.first_name,
            last_name=user.last_name,
            role=user.role,
            branch_id=user.branch_id,
            active=user.active,
            created_at=user.created_at,
            last_login=user.last_login
        ) for user in users
    ]


@router.get("/me", response_model=UserResponse)
async def get_my_user(current_user: User = Depends(get_current_user)):
    """Return the authenticated user's own profile."""
    return current_user


@router.get("/{user_id}", response_model=UserDetailResponse)
async def get_user(user_id: str, db: Session = Depends(get_db)):
    """Get a specific user by ID with full details"""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    return UserDetailResponse(
        id=user.id,
        username=user.username,
        email=user.email,
        first_name=user.first_name,
        last_name=user.last_name,
        phone=user.phone,
        address=user.address,
        role=user.role,
        branch_id=user.branch_id,
        branch_name=user.branch.name if user.branch else None,
        branch_code=user.branch.code if user.branch else None,
        active=user.active,
        created_at=user.created_at,
        updated_at=user.updated_at,
        last_login=user.last_login,
        login_count=user.login_count,
        notes=user.notes
    )


@router.post("/", response_model=UserCreateResponse)
async def create_user(
    user_data: UserCreateRequest,
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
    current_user: User = Depends(get_current_user),
):
    """Create a new user with proper branch assignment validation"""
    if (current_user.role or "").lower() not in ("super_admin", "admin"):
        from app.core.cnp_support import is_cnp_support_user
        if not is_cnp_support_user(current_user):
            raise HTTPException(status_code=403, detail="Only administrators can create users")

    # Validate role and branch assignment rules
    if user_data.role in BRANCH_SPECIFIC_ROLES:
        if not user_data.branch_id:
            raise HTTPException(
                status_code=400,
                detail=f"Users with role '{user_data.role}' must be assigned to a specific branch"
            )

        # Verify branch exists and is active
        branch = db.query(Branch).filter(
            Branch.id == user_data.branch_id,
            Branch.active == True
        ).first()
        if not branch:
            raise HTTPException(status_code=400, detail="Invalid or inactive branch")

    elif user_data.role in GLOBAL_ROLES:
        # Global roles don't require branch assignment (but can have one)
        if user_data.branch_id:
            branch = db.query(Branch).filter(Branch.id == user_data.branch_id).first()
            if not branch:
                raise HTTPException(status_code=400, detail="Invalid branch")
    else:
        raise HTTPException(status_code=400, detail=f"Invalid role: {user_data.role}")

    from app.utils.user_email import normalize_user_email

    email = normalize_user_email(user_data.email)
    if db.query(User).filter(User.username == user_data.username).first():
        raise HTTPException(status_code=400, detail="Username already exists")
    if email:
        email_q = db.query(User).filter(func.lower(User.email) == email.lower())
        if company_id:
            email_q = email_q.filter(User.company_id == company_id)
        if email_q.first():
            raise HTTPException(status_code=400, detail="Email already exists for this company")

    from app.services.login_credentials_service import generate_temp_password
    from app.services.whatsapp_service import WhatsAppService

    plain_password = (user_data.password or "").strip()
    must_change = False
    if user_data.generate_temp_password:
        plain_password = generate_temp_password()
        must_change = True
    elif not plain_password:
        raise HTTPException(
            status_code=400,
            detail="Password is required, or enable generate_temp_password.",
        )

    # Create new user
    try:
        new_user = User(
            id=str(uuid4()),
            username=user_data.username,
            email=email,
            password_digest=get_password_hash(plain_password),
            first_name=user_data.first_name,
            last_name=user_data.last_name,
            phone=user_data.phone or user_data.whatsapp_phone,
            address=user_data.address,
            role=user_data.role,
            branch_id=user_data.branch_id,
            company_id=company_id,
            active=True,
            notes=user_data.notes,
            password_changed_at=datetime.utcnow(),
            must_change_password=must_change,
        )

        db.add(new_user)
        db.commit()
        db.refresh(new_user)

        whatsapp_sent = False
        whatsapp_message = None
        if user_data.send_whatsapp_login:
            wa = WhatsAppService(db)
            if not wa.is_enabled():
                whatsapp_message = "WhatsApp not configured — save login details manually."
            else:
                from app.services.login_credentials_service import build_login_message_for_user

                phone = (user_data.whatsapp_phone or new_user.phone or "").strip()
                if not phone:
                    whatsapp_message = "No phone number — enter phone to send WhatsApp login."
                else:
                    card = build_login_message_for_user(
                        db,
                        user=new_user,
                        company_id=company_id,
                        password_plain=plain_password if must_change else None,
                        prefer_cloud=user_data.prefer_cloud_login,
                    )
                    result = wa.send_custom_message(phone, card["message"])
                    whatsapp_sent = bool(result.get("success"))
                    whatsapp_message = (
                        "Login sent via WhatsApp"
                        if whatsapp_sent
                        else (result.get("error") or "WhatsApp send failed")
                    )
                    if phone != (new_user.phone or ""):
                        new_user.phone = phone
                        db.commit()

        return UserCreateResponse(
            id=new_user.id,
            username=new_user.username,
            email=new_user.email,
            first_name=new_user.first_name,
            last_name=new_user.last_name,
            role=new_user.role,
            branch_id=new_user.branch_id,
            active=new_user.active,
            created_at=new_user.created_at,
            last_login=new_user.last_login,
            temporary_password=plain_password if must_change else None,
            whatsapp_sent=whatsapp_sent,
            whatsapp_message=whatsapp_message,
        )

    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=400,
            detail="Username or email already exists"
        )


@router.put("/{user_id}", response_model=UserResponse)
async def update_user(
    user_id: str,
    user_data: UserUpdateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Update user information with branch assignment validation"""
    _assert_can_manage_users(current_user)

    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    if company_id and user.company_id and user.company_id != company_id:
        raise HTTPException(status_code=403, detail="User is not in your company")

    # Update fields if provided
    update_data = user_data.dict(exclude_unset=True)

    # Handle password update
    if 'password' in update_data:
        update_data['password_digest'] = get_password_hash(update_data.pop('password'))
        update_data['password_changed_at'] = datetime.utcnow()

    # Validate role and branch changes
    new_role = update_data.get('role', user.role)
    new_branch_id = update_data.get('branch_id', user.branch_id)

    if new_role in BRANCH_SPECIFIC_ROLES and not new_branch_id:
        raise HTTPException(
            status_code=400,
            detail=f"Users with role '{new_role}' must be assigned to a specific branch"
        )

    if "branch_id" in update_data and update_data["branch_id"]:
        branch = db.query(Branch).filter(
            Branch.id == update_data["branch_id"],
            Branch.active == True,
        ).first()
        if not branch:
            raise HTTPException(status_code=400, detail="Invalid or inactive branch")
        _assert_can_transfer_user(current_user, user, branch, company_id)

    # Update user
    for field, value in update_data.items():
        setattr(user, field, value)

    user.updated_at = datetime.utcnow()

    try:
        db.commit()
        db.refresh(user)

        return UserResponse(
            id=user.id,
            username=user.username,
            email=user.email,
            first_name=user.first_name,
            last_name=user.last_name,
            role=user.role,
            branch_id=user.branch_id,
            active=user.active,
            created_at=user.created_at,
            last_login=user.last_login
        )
    except IntegrityError:
        db.rollback()
        raise HTTPException(status_code=400, detail="Username or email already exists")


@router.post("/{user_id}/assign-branch")
async def assign_user_to_branch(
    user_id: str,
    assignment: BranchAssignmentRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Assign or reassign a user to a branch (managers, accountants, admins)."""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    branch = db.query(Branch).filter(
        Branch.id == assignment.branch_id,
        Branch.active == True,
    ).first()
    if not branch:
        raise HTTPException(status_code=400, detail="Invalid or inactive branch")

    _assert_can_transfer_user(current_user, user, branch, company_id)

    if user.role in BRANCH_SPECIFIC_ROLES or user.role in GLOBAL_ROLES:
        user.branch_id = assignment.branch_id
    else:
        raise HTTPException(status_code=400, detail="Invalid user role for branch assignment")

    user.updated_at = datetime.utcnow()
    db.commit()

    return {
        "message": f"User {user.username} successfully assigned to branch {branch.name}",
        "user_id": user.id,
        "branch_id": branch.id,
        "branch_code": branch.code,
        "branch_name": branch.name,
    }


@router.post("/{user_id}/transfer-branch")
async def transfer_user_branch(
    user_id: str,
    assignment: BranchAssignmentRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
    company_id: Optional[str] = Depends(get_current_company_id),
    request: Request = None,
):
    """Move a user to another branch when they relocate (managers & accountants)."""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    branch = db.query(Branch).filter(
        Branch.id == assignment.branch_id,
        Branch.active == True,
    ).first()
    if not branch:
        raise HTTPException(status_code=400, detail="Invalid or inactive branch")

    _assert_can_transfer_user(current_user, user, branch, company_id)

    if user.role in BRANCH_SPECIFIC_ROLES and not assignment.branch_id:
        raise HTTPException(
            status_code=400,
            detail=f"Users with role '{user.role}' must be assigned to a branch",
        )

    old_branch_id = user.branch_id
    user.branch_id = assignment.branch_id
    user.updated_at = datetime.utcnow()
    db.commit()

    try:
        log_user_action(
            db,
            current_user,
            action="transfer_branch",
            module="users",
            resource_type="user",
            resource_id=user.id,
            details={
                "from_branch_id": old_branch_id,
                "to_branch_id": branch.id,
                "to_branch_name": branch.name,
            },
            request=request,
        )
    except Exception:
        pass

    return {
        "message": f"{user.username} transferred to {branch.name}",
        "user_id": user.id,
        "branch_id": branch.id,
        "branch_code": branch.code,
        "branch_name": branch.name,
        "previous_branch_id": old_branch_id,
    }


@router.post("/{user_id}/toggle-status")
async def toggle_user_status(user_id: str, db: Session = Depends(get_db)):
    """Activate or deactivate a user"""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    user.active = not user.active
    user.updated_at = datetime.utcnow()
    db.commit()

    status_text = "activated" if user.active else "deactivated"
    return {
        "message": f"User {user.username} has been {status_text}",
        "user_id": user.id,
        "active": user.active
    }


@router.get("/branch/{branch_id}/users", response_model=List[UserResponse])
async def get_branch_users(branch_id: str, db: Session = Depends(get_db)):
    """Get all users who can access a specific branch"""
    branch = db.query(Branch).filter(Branch.id == branch_id).first()
    if not branch:
        raise HTTPException(status_code=404, detail="Branch not found")

    # Get branch-specific users
    branch_users = db.query(User).filter(
        User.branch_id == branch_id,
        User.active == True
    ).all()

    # Get global users (they can access this branch too)
    global_users = db.query(User).filter(
        User.role.in_(GLOBAL_ROLES),
        User.active == True
    ).all()

    # Combine and deduplicate
    all_users = []
    user_ids = set()

    for user in branch_users + global_users:
        if user.id not in user_ids:
            all_users.append(UserResponse(
                id=user.id,
                username=user.username,
                email=user.email,
                first_name=user.first_name,
                last_name=user.last_name,
                role=user.role,
                branch_id=user.branch_id,
                active=user.active,
                created_at=user.created_at,
                last_login=user.last_login
            ))
            user_ids.add(user.id)

    return all_users


@router.get("/roles/summary")
async def get_roles_summary(db: Session = Depends(get_db)):
    """Get summary of user roles and their branch access rules"""
    return {
        "global_roles": {
            "roles": list(GLOBAL_ROLES),
            "description": "Can access any branch",
            "branch_required": False
        },
        "branch_specific_roles": {
            "roles": list(BRANCH_SPECIFIC_ROLES),
            "description": "Must be assigned to a specific branch",
            "branch_required": True
        }
    }


@router.get("/roles/summary")
async def get_roles_summary(db: Session = Depends(get_db)):
    """Get summary of user roles and their branch access rules"""
    return {
        "global_roles": {
            "roles": list(GLOBAL_ROLES),
            "description": "Can access any branch",
            "branch_required": False
        },
        "branch_specific_roles": {
            "roles": list(BRANCH_SPECIFIC_ROLES),
            "description": "Must be assigned to a specific branch",
            "branch_required": True
        }
    }

    # Attach pagination metadata via response headers using a FastAPI workaround (return list but set headers in dependency) - simpler: we cannot set here w/out Response; we'll include in custom header if response object passed.
    # For now: client can read X-Total-Count via middleware configured globally (optional). TODO: refactor signature to include Response if needed.
    # We'll try import Response elegantly.
    from fastapi import Response
    # Provide header with total count.
    Response(headers={"X-Total-Count": str(total)} )
    return users


@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: str, db: Session = Depends(get_db)):  # current_user parameter removed for development
    """Get user by ID"""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user


@router.put("/{user_id}", response_model=UserResponse)
async def update_user(
    user_id: str,
    user_update: UserUpdate,
    db: Session = Depends(get_db),
    # current_user parameter removed for development),
    request: Request = None
):
    """Update a user (email uniqueness enforced)."""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    from app.utils.user_email import normalize_user_email

    update_data = user_update.dict(exclude_unset=True)
    if "email" in update_data:
        update_data["email"] = normalize_user_email(update_data.get("email"))

    # Enforce unique email per company if updating
    if update_data.get("email"):
        email_l = update_data["email"].lower()
        email_q = db.query(User).filter(func.lower(User.email) == email_l, User.id != user.id)
        if user.company_id:
            email_q = email_q.filter(User.company_id == user.company_id)
        if email_q.first():
            raise HTTPException(status_code=400, detail="Email already in use")

    for field, value in update_data.items():
        setattr(user, field, value)
    try:
        db.commit()
        db.refresh(user)
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'email' in error_msg:
                raise HTTPException(status_code=400, detail="Email already exists")
            raise HTTPException(status_code=400, detail="A user with this email already exists")
        raise HTTPException(status_code=400, detail=f"Database constraint violation: {error_msg}")

    try:
        log_user_action(db, None, action="update", module="users", resource_type="user", resource_id=user.id, details=list(update_data.keys()), request=request)
    except Exception:
        pass

    return user


@router.delete("/{user_id}")
async def delete_user(
    user_id: str,
    db: Session = Depends(get_db),
    # current_user parameter removed for development),
    request: Request = None
):
    """Soft delete a user (set active False)."""
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    if not user.active:
        return {"message": "User already inactive"}

    user.active = False
    db.commit()
    try:
        log_user_action(db, None, action="deactivate", module="users", resource_type="user", resource_id=user.id, details=None, request=request)
    except Exception:
        pass
    return {"message": "User deactivated"}


@router.post("/{user_id}/change-password")
async def change_password(
    user_id: str,
    password_data: dict,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Change user password (self or admin)."""
    from app.core.security import get_password_hash, verify_password

    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    if "new_password" not in password_data:
        raise HTTPException(status_code=400, detail="New password is required")

    from app.core.cnp_support import is_cnp_support_user

    actor_role = (current_user.role or "").lower()
    is_self = str(current_user.id) == str(user_id)
    if not is_self and actor_role not in ("super_admin", "admin") and not is_cnp_support_user(current_user):
        raise HTTPException(status_code=403, detail="Cannot change another user's password")

    if is_self:
        current_pw = password_data.get("current_password") or password_data.get("old_password")
        if not current_pw or not verify_password(current_pw, user.password_digest):
            raise HTTPException(status_code=400, detail="Current password is incorrect")

    user.password_digest = get_password_hash(password_data["new_password"])
    db.commit()
    db.refresh(user)

    try:
        log_user_action(
            db, current_user, action="change_password", module="users",
            resource_type="user", resource_id=user.id, details={"self": is_self},
        )
    except Exception:
        pass
    return {"message": "Password changed successfully"}


