"""
Admin API endpoints for managing configuration and users.
Endpoints require session auth and specific permissions (e.g. admin.users, admin.manage_roles).
"""
import os
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict, List
from sqlalchemy.orm import Session
from passlib.context import CryptContext

from app.database import get_db
from app.models import User, Role, Permission, user_permissions
from app.routers.auth import require_admin_role, get_user_permissions, require_permission
from app.services.email_service import send_invite_email
from app.config.config_storage import get_config_storage
from app.config import settings
from app.config.env_file import get_connections_masked, update_env_file
from app.services.xero_cache import xero_cache
import logging

logger = logging.getLogger(__name__)
pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")

router = APIRouter(prefix="/api/admin", tags=["admin"])


class TargetBreakevenUpdate(BaseModel):
    """Schema for updating target and breakeven."""
    target: Optional[int] = None
    breakeven: Optional[int] = None


class GPThresholdsUpdate(BaseModel):
    """Schema for updating GP color thresholds."""
    green_min: float
    yellow_min: float
    yellow_max: float
    red_max: float


class ConfigResponse(BaseModel):
    """Response schema for configuration."""
    targets_breakevens: Dict
    gp_thresholds: Dict
    branches: Dict


class CreateUserBody(BaseModel):
    """Schema for creating a user. Assign permissions directly via permission_keys."""
    username: str
    password: str
    permission_keys: Optional[List[str]] = None  # direct permissions; if omitted, role_id used to copy role permissions
    role_id: Optional[int] = None  # optional: for display; if permission_keys omitted, role permissions copied to user


class InviteUserBody(BaseModel):
    """Schema for inviting a user by email. They set password via link. Assign permissions via permission_keys."""
    email: str
    permission_keys: List[str]  # direct permissions for the new user


class CreateRoleBody(BaseModel):
    name: str
    permission_keys: List[str] = []


class UpdateRoleBody(BaseModel):
    name: Optional[str] = None
    permission_keys: Optional[List[str]] = None


class AssignRoleBody(BaseModel):
    role_id: int


class UserPermissionsBody(BaseModel):
    permission_keys: List[str]


class UpdateUserEmailBody(BaseModel):
    username: str  # New email/username


@router.get("/config", response_model=ConfigResponse)
async def get_config(user: User = Depends(require_permission("admin.revenue_targets"))):
    """
    Get all configuration (targets, breakevens, GP thresholds).
    Requires admin authentication.
    """
    storage = get_config_storage()
    
    return ConfigResponse(
        targets_breakevens=storage.get_targets_breakevens(),
        gp_thresholds=storage.get_gp_thresholds(),
        branches={
            branch_id: {
                "name": branch_config["name"],
                "companies": {
                    str(company_id): company_info
                    for company_id, company_info in branch_config["companies"].items()
                }
            }
            for branch_id, branch_config in settings.BRANCHES.items()
        }
    )


@router.put("/targets-breakevens/{branch_id}/{company_id}")
async def update_target_breakeven(
    branch_id: str,
    company_id: int,
    update: TargetBreakevenUpdate,
    user: User = Depends(require_permission("admin.revenue_targets")),
):
    """
    Update target and/or breakeven for a specific branch/company.
    Requires admin authentication.
    
    Args:
        branch_id: Branch identifier (branch1, branch2, branch3)
        company_id: Company/department ID
        update: Target and/or breakeven values to update
    """
    # Validate branch_id
    if branch_id not in settings.BRANCHES:
        raise HTTPException(status_code=404, detail=f"Branch {branch_id} not found")
    
    # Validate company_id exists in branch
    branch_config = settings.BRANCHES[branch_id]
    if company_id not in branch_config["companies"]:
        raise HTTPException(
            status_code=404, 
            detail=f"Company {company_id} not found in branch {branch_id}"
        )
    
    storage = get_config_storage()
    storage.update_target_breakeven(
        branch_id=branch_id,
        company_id=company_id,
        target=update.target,
        breakeven=update.breakeven
    )
    
    return {
        "message": "Target/breakeven updated successfully",
        "branch_id": branch_id,
        "company_id": company_id,
        "target": update.target,
        "breakeven": update.breakeven
    }


@router.get("/gp-thresholds", response_model=Dict)
async def get_gp_thresholds(user: User = Depends(require_permission("admin.gp_thresholds"))):
    """
    Get all GP color thresholds.
    Requires admin authentication.
    """
    storage = get_config_storage()
    return storage.get_gp_thresholds()


@router.put("/gp-thresholds/{department_type}")
async def update_gp_thresholds(
    department_type: str,
    thresholds: GPThresholdsUpdate,
    user: User = Depends(require_permission("admin.gp_thresholds")),
):
    """
    Update GP color thresholds for a department type.
    Requires admin authentication.
    
    Args:
        department_type: Department type (electrical, commercial, air, solar, residential)
        thresholds: New threshold values
    """
    # Validate thresholds (ranges can meet at boundaries)
    if thresholds.yellow_min >= thresholds.yellow_max:
        raise HTTPException(
            status_code=400,
            detail="yellow_min must be less than yellow_max"
        )
    
    if thresholds.green_min < thresholds.yellow_max:
        raise HTTPException(
            status_code=400,
            detail="green_min must be greater than or equal to yellow_max"
        )
    
    if thresholds.red_max > thresholds.yellow_min:
        raise HTTPException(
            status_code=400,
            detail="red_max must be less than or equal to yellow_min"
        )
    
    storage = get_config_storage()
    storage.update_gp_thresholds(
        department_type=department_type,
        thresholds={
            "green_min": thresholds.green_min,
            "yellow_min": thresholds.yellow_min,
            "yellow_max": thresholds.yellow_max,
            "red_max": thresholds.red_max
        }
    )
    
    return {
        "message": "GP thresholds updated successfully",
        "department_type": department_type,
        "thresholds": thresholds.dict()
    }


@router.get("/kpi-targets")
async def get_kpi_targets(user: User = Depends(require_permission("admin.kpi_targets"))):
    """
    Get all KPI targets (defaults merged with admin overrides).
    Requires admin authentication.
    """
    storage = get_config_storage()
    return storage.get_kpi_targets()


@router.put("/kpi-targets")
async def update_all_kpi_targets(
    body: dict,
    user: User = Depends(require_permission("admin.kpi_targets")),
):
    """
    Bulk update KPI targets.
    Body: { branch: { department: { metricKey: { target: N, weight: N, ... } } } }
    Requires admin authentication.
    """
    storage = get_config_storage()
    storage.update_all_kpi_targets(body)
    return {"status": "ok"}


@router.put("/kpi-targets/{branch}/{department}/{metric_key}")
async def update_kpi_target(
    branch: str,
    department: str,
    metric_key: str,
    body: dict,
    user: User = Depends(require_permission("admin.kpi_targets")),
):
    """
    Update a specific KPI target metric.
    Requires admin authentication.

    Args:
        branch: Branch name (e.g. Busselton, Bunbury, Mandurah)
        department: Department name (e.g. Electrical, Air, Commercial)
        metric_key: Metric key (e.g. quoteConversion, clientReviews)
        body: Fields to update (e.g. {"target": 70, "weight": 25})
    """
    storage = get_config_storage()
    storage.update_kpi_targets(branch, department, metric_key, body)
    return {"status": "ok"}


@router.get("/cache-stats")
async def get_cache_stats(user: User = Depends(require_permission("admin.access"))):
    """
    Get Xero API cache statistics.
    Shows cache hits, misses, hit rate, and current cache size.
    Requires admin authentication.
    """
    return xero_cache.get_stats()


@router.post("/cache-clear")
async def clear_cache(user: User = Depends(require_permission("admin.access"))):
    """
    Clear the entire Xero API cache.
    Forces fresh data fetch on next request.
    Requires admin authentication.
    """
    xero_cache.clear()
    return {"message": "Cache cleared successfully"}


@router.get("/connections")
async def get_connections(user: User = Depends(require_permission("admin.connections"))):
    """
    Get connection-related env vars (Xero, Google Sheets, etc.) for the Admin Connections tab.
    Secrets are masked. Updates are written to backend .env file.
    """
    return get_connections_masked()


@router.put("/connections")
async def update_connections(
    body: Dict[str, str],
    user: User = Depends(require_permission("admin.connections")),
):
    """
    Update connection-related env vars. Only allowlisted keys are written to .env.
    Omit or send empty string for a secret to keep the current value.
    Restart the backend for Xero/Google changes to take effect.
    """
    from app.config.env_file import CONNECTIONS_ALLOWLIST, SECRET_KEYS
    updates = {}
    for k, v in body.items():
        if k not in CONNECTIONS_ALLOWLIST:
            continue
        val = (v or "").strip()
        if k in SECRET_KEYS and not val:
            continue
        updates[k] = val
    update_env_file(updates)
    return {"message": "Connections updated. Restart the backend for changes to take effect."}


def _user_role_info(db: Session, u: User) -> dict:
    """Return role_id, role_name, and effective role slug for a user."""
    role = db.query(Role).filter(Role.id == u.role_id).first() if u.role_id else db.query(Role).filter(Role.slug == (u.role or "user").lower()).first()
    if role:
        return {"role_id": u.role_id, "role_name": role.name, "role": role.slug or u.role}
    return {"role_id": None, "role_name": (u.role or "user").capitalize(), "role": u.role or "user"}


def _user_permission_keys(db: Session, user_id: int) -> List[str]:
    """Return list of permission keys directly assigned to the user."""
    rows = (
        db.query(Permission.key)
        .join(user_permissions, user_permissions.c.permission_id == Permission.id)
        .filter(user_permissions.c.user_id == user_id)
        .all()
    )
    return [r[0] for r in rows]


@router.get("/users")
async def list_users(
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """List all users with id, username, role, role_id, role_name, permission_keys, created_at."""
    users = db.query(User).order_by(User.created_at.desc()).all()
    return {
        "users": [
            {
                "id": u.id,
                "username": u.username,
                "created_at": u.created_at.isoformat() if u.created_at else None,
                "invite_pending": bool(getattr(u, "invite_token", None) and getattr(u, "invite_expires_at", None)),
                "permission_keys": _user_permission_keys(db, u.id),
                **_user_role_info(db, u),
            }
            for u in users
        ]
    }


def _set_user_permissions(db: Session, user_id: int, permission_keys: List[str]) -> None:
    """Replace user's direct permissions with the given keys."""
    db.execute(user_permissions.delete().where(user_permissions.c.user_id == user_id))
    if permission_keys:
        perms = db.query(Permission).filter(Permission.key.in_(permission_keys)).all()
        for p in perms:
            db.execute(user_permissions.insert().values(user_id=user_id, permission_id=p.id))


@router.post("/users")
async def create_user(
    body: CreateUserBody,
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """Create a new user. Assign permissions via permission_keys; optional role_id for display or to copy from role if no keys."""
    existing = db.query(User).filter(User.username == body.username).first()
    if existing:
        raise HTTPException(status_code=400, detail="Username already exists")
    permission_keys = list(body.permission_keys) if body.permission_keys else []
    role = None
    if body.role_id is not None:
        role = db.query(Role).filter(Role.id == body.role_id).first()
        if not role:
            raise HTTPException(status_code=400, detail="Role not found")
    if not permission_keys and role:
        permission_keys = [p.key for p in role.permissions]
    if not permission_keys:
        raise HTTPException(status_code=400, detail="Provide permission_keys or role_id to copy permissions from")
    effective_role = role.slug if role and role.slug else (role.name.lower() if role and role.name else "user")
    role_id = role.id if role else None
    password_hash = pwd_ctx.hash(body.password)
    new_user = User(username=body.username, password_hash=password_hash, role=effective_role, role_id=role_id)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    _set_user_permissions(db, new_user.id, permission_keys)
    db.commit()
    info = _user_role_info(db, new_user)
    return {
        "id": new_user.id,
        "username": new_user.username,
        "role": info["role"],
        "role_id": info["role_id"],
        "role_name": info["role_name"],
        "permission_keys": _user_permission_keys(db, new_user.id),
        "created_at": new_user.created_at.isoformat() if new_user.created_at else None,
    }


@router.patch("/users/{user_id}")
async def update_user_email(
    user_id: int,
    body: UpdateUserEmailBody,
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """Update a user's email (username) by admin. No verification required when updated manually."""
    new_email = (body.username or "").strip().lower()
    if not new_email or "@" not in new_email:
        raise HTTPException(status_code=400, detail="Valid email required")
    target = db.query(User).filter(User.id == user_id).first()
    if not target:
        raise HTTPException(status_code=404, detail="User not found")
    existing = db.query(User).filter(User.username == new_email, User.id != user_id).first()
    if existing:
        raise HTTPException(status_code=400, detail="A user with this email already exists")
    target.username = new_email
    db.commit()
    return {"ok": True, "username": target.username}


@router.delete("/users/{user_id}")
async def delete_user(
    user_id: int,
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """Delete a user (or pending invite). Requires admin.users permission."""
    if user.id == user_id:
        raise HTTPException(status_code=400, detail="You cannot delete your own account")
    target = db.query(User).filter(User.id == user_id).first()
    if not target:
        raise HTTPException(status_code=404, detail="User not found")
    db.delete(target)
    db.commit()
    return {"ok": True}


@router.post("/invites")
async def invite_user(
    body: InviteUserBody,
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """Invite a new user by email. Creates user with pending invite and assigned permission_keys. They set password via email link."""
    import secrets
    from datetime import datetime, timedelta
    email = (body.email or "").strip().lower()
    if not email or "@" not in email:
        raise HTTPException(status_code=400, detail="Valid email required")
    existing = db.query(User).filter(User.username == email).first()
    if existing:
        raise HTTPException(status_code=400, detail="A user with this email already exists")
    if not body.permission_keys:
        raise HTTPException(status_code=400, detail="permission_keys is required")
    invite_token = secrets.token_urlsafe(32)
    invite_expires_at = datetime.utcnow() + timedelta(days=7)
    placeholder_hash = pwd_ctx.hash(secrets.token_urlsafe(24))
    new_user = User(
        username=email,
        password_hash=placeholder_hash,
        role="user",
        role_id=None,
        invite_token=invite_token,
        invite_expires_at=invite_expires_at,
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    _set_user_permissions(db, new_user.id, body.permission_keys)
    db.commit()
    app_url = (os.getenv("APP_URL") or os.getenv("FRONTEND_URL") or "").strip()
    if not app_url:
        if os.path.exists("/.dockerenv") or os.getenv("RUNNING_IN_DOCKER", "").lower() in ("1", "true", "yes"):
            app_url = "https://nixonstats.info"
        else:
            app_url = "http://localhost:3000"
    app_url = app_url.rstrip("/")
    invite_link = f"{app_url}/set-password?token={invite_token}"
    role_name = "User"
    logger.info("Invite link for %s: %s", email, invite_link)
    sent = send_invite_email(email, invite_link, role_name)
    return {
        "id": new_user.id,
        "username": new_user.username,
        "role": "user",
        "role_id": None,
        "role_name": role_name,
        "permission_keys": _user_permission_keys(db, new_user.id),
        "invite_expires_at": new_user.invite_expires_at.isoformat() if new_user.invite_expires_at else None,
        "email_sent": sent,
    }


# ---------- Permissions (read-only for admin; director uses for role editor) ----------

@router.get("/permissions")
async def list_permissions(
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """List all permissions grouped by category. Admin or Director only."""
    perms = db.query(Permission).order_by(Permission.category, Permission.key).all()
    by_cat = {}
    for p in perms:
        by_cat.setdefault(p.category, []).append({"id": p.id, "key": p.key, "name": p.name})
    return {"permissions": by_cat, "all": [{"id": p.id, "key": p.key, "name": p.name, "category": p.category} for p in perms]}


# ---------- Roles (admin.manage_roles: create, update, delete, assign to user) ----------

@router.get("/roles")
async def list_roles(
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """List all roles with their permission keys. Admin or Director only."""
    roles = db.query(Role).order_by(Role.is_system.desc(), Role.name).all()
    return {
        "roles": [
            {
                "id": r.id,
                "name": r.name,
                "slug": r.slug,
                "is_system": r.is_system,
                "permission_keys": [p.key for p in r.permissions],
            }
            for r in roles
        ]
    }


@router.post("/roles")
async def create_role(
    body: CreateRoleBody,
    user: User = Depends(require_permission("admin.manage_roles")),
    db: Session = Depends(get_db),
):
    """Create a custom role. Requires admin.manage_roles permission."""
    existing = db.query(Role).filter(Role.name == body.name).first()
    if existing:
        raise HTTPException(status_code=400, detail="Role name already exists")
    perms = db.query(Permission).filter(Permission.key.in_(body.permission_keys)).all() if body.permission_keys else []
    role = Role(name=body.name, slug=None, is_system=False)
    db.add(role)
    db.flush()
    role.permissions = perms
    db.commit()
    db.refresh(role)
    return {"id": role.id, "name": role.name, "slug": role.slug, "is_system": role.is_system, "permission_keys": [p.key for p in role.permissions]}


@router.put("/roles/{role_id}")
async def update_role(
    role_id: int,
    body: UpdateRoleBody,
    user: User = Depends(require_permission("admin.manage_roles")),
    db: Session = Depends(get_db),
):
    """Update role name and/or permissions. Requires admin.manage_roles. System roles: only name if allowed, permissions are fixed."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(status_code=404, detail="Role not found")
    if body.name is not None:
        role.name = body.name
    if body.permission_keys is not None and not role.is_system:
        perms = db.query(Permission).filter(Permission.key.in_(body.permission_keys)).all()
        role.permissions = perms
    db.commit()
    db.refresh(role)
    return {"id": role.id, "name": role.name, "slug": role.slug, "is_system": role.is_system, "permission_keys": [p.key for p in role.permissions]}


@router.delete("/roles/{role_id}")
async def delete_role(
    role_id: int,
    user: User = Depends(require_permission("admin.manage_roles")),
    db: Session = Depends(get_db),
):
    """Delete a custom role. Requires admin.manage_roles. System roles cannot be deleted."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(status_code=404, detail="Role not found")
    if role.is_system:
        raise HTTPException(status_code=400, detail="System roles cannot be deleted")
    db.delete(role)
    db.commit()
    return {"ok": True}


@router.put("/users/{user_id}/permissions")
async def set_user_permissions(
    user_id: int,
    body: UserPermissionsBody,
    user: User = Depends(require_permission("admin.users")),
    db: Session = Depends(get_db),
):
    """Set a user's direct permissions. Replaces any existing direct permissions. Requires admin.users."""
    target = db.query(User).filter(User.id == user_id).first()
    if not target:
        raise HTTPException(status_code=404, detail="User not found")
    _set_user_permissions(db, user_id, body.permission_keys or [])
    db.commit()
    return {
        "id": target.id,
        "username": target.username,
        "permission_keys": _user_permission_keys(db, user_id),
    }


@router.put("/users/{user_id}/role")
async def assign_user_role(
    user_id: int,
    body: AssignRoleBody,
    user: User = Depends(require_permission("admin.manage_roles")),
    db: Session = Depends(get_db),
):
    """Assign a role to a user (legacy/display). Copies role permissions to user's direct permissions. Requires admin.manage_roles."""
    target = db.query(User).filter(User.id == user_id).first()
    if not target:
        raise HTTPException(status_code=404, detail="User not found")
    role = db.query(Role).filter(Role.id == body.role_id).first()
    if not role:
        raise HTTPException(status_code=404, detail="Role not found")
    target.role_id = body.role_id
    target.role = role.slug if role.slug else role.name.lower()
    permission_keys = [p.key for p in role.permissions]
    _set_user_permissions(db, user_id, permission_keys)
    db.commit()
    db.refresh(target)
    info = _user_role_info(db, target)
    return {"id": target.id, "username": target.username, "role": info["role"], "role_id": info["role_id"], "role_name": info["role_name"], "permission_keys": permission_keys}


@router.get("/sheets-debug")
async def sheets_debug(user: User = Depends(require_permission("admin.connections"))):
    """
    Debug endpoint: returns raw data from the Google Sheet (first 20 rows).
    Use this to verify the sheet is being read correctly (e.g. check E2 shows 'test').
    Requires admin authentication.
    """
    from app.services.sheets_service import sheets_service
    sheets_service._initialize()
    if not sheets_service._service:
        return {"error": "Google Sheets service not initialized", "hint": "Check GOOGLE_SHEETS_ID and credentials"}
    try:
        import os
        sheet_name = os.getenv("GOOGLE_SHEETS_TAB", "Bunbury Electrics")
        result = sheets_service._service.spreadsheets().values().get(
            spreadsheetId=sheets_service.spreadsheet_id,
            range=f"{sheet_name}!A1:E20",
            valueRenderOption="FORMATTED_VALUE"
        ).execute()
        rows = result.get("values", [])
        return {
            "rows": len(rows),
            "data": rows,
            "note": "Columns A-E, first 20 rows. E2 should show 'test' if sheet is connected."
        }
    except Exception as e:
        return {"error": str(e), "type": type(e).__name__}
