from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import current_user
from sqlalchemy import func, or_

from .extensions import db
from .main import permission_required
from .models import AuditLog, Branch, Department, Permission, Role, User
from .services import audit


bp = Blueprint("admin", __name__, url_prefix="/admin")


@bp.route("/branches", methods=["GET", "POST"])
@permission_required("admin.branches")
def branches():
    if request.method == "POST":
        code = request.form.get("code", "").strip().upper()
        name = request.form.get("name", "").strip()
        if not code or not name:
            flash("Branch code and name are required.", "danger")
        elif db.session.scalar(db.select(Branch).where(Branch.code == code)):
            flash("Branch code already exists.", "danger")
        else:
            branch = Branch(code=code, name=name, address=request.form.get("address", "").strip() or None)
            db.session.add(branch)
            db.session.flush()
            audit("BRANCH_CREATED", "Branch", branch.id, name)
            db.session.commit()
            flash("Branch added.", "success")
        return redirect(url_for("admin.branches"))
    rows = db.session.scalars(db.select(Branch).order_by(Branch.name)).all()
    return render_template("admin/branches.html", branches=rows)


@bp.post("/branches/<int:branch_id>/toggle")
@permission_required("admin.branches")
def branch_toggle(branch_id):
    branch = db.get_or_404(Branch, branch_id)
    branch.is_active = not branch.is_active
    audit("BRANCH_STATUS_CHANGED", "Branch", branch.id, f"Active={branch.is_active}")
    db.session.commit()
    flash("Branch status updated.", "success")
    return redirect(url_for("admin.branches"))


@bp.route("/departments", methods=["GET", "POST"])
@permission_required("admin.branches")
def departments():
    branches = db.session.scalars(db.select(Branch).order_by(Branch.name)).all()
    if request.method == "POST":
        branch = db.session.get(Branch, request.form.get("branch_id", type=int))
        code = request.form.get("code", "").strip().upper()
        name = request.form.get("name", "").strip()
        duplicate = db.session.scalar(
            db.select(Department).where(Department.branch_id == getattr(branch, "id", None), Department.name == name)
        )
        if not branch or not code or not name:
            flash("Branch, department code, and name are required.", "danger")
        elif duplicate:
            flash("That department already exists in the selected branch.", "danger")
        else:
            department = Department(branch=branch, code=code, name=name)
            db.session.add(department)
            db.session.flush()
            audit("DEPARTMENT_CREATED", "Department", department.id, f"{branch.name}: {name}")
            db.session.commit()
            flash("Department added.", "success")
        return redirect(url_for("admin.departments"))
    rows = db.session.scalars(db.select(Department).order_by(Department.branch_id, Department.name)).all()
    return render_template("admin/departments.html", departments=rows, branches=branches)


@bp.post("/departments/<int:department_id>/toggle")
@permission_required("admin.branches")
def department_toggle(department_id):
    department = db.get_or_404(Department, department_id)
    department.is_active = not department.is_active
    audit("DEPARTMENT_STATUS_CHANGED", "Department", department.id, f"Active={department.is_active}")
    db.session.commit()
    flash("Department status updated.", "success")
    return redirect(url_for("admin.departments"))


@bp.route("/users", methods=["GET", "POST"])
@permission_required("admin.users")
def users():
    roles = db.session.scalars(db.select(Role).order_by(Role.name)).all()
    branches = db.session.scalars(db.select(Branch).order_by(Branch.name)).all()
    departments = db.session.scalars(db.select(Department).order_by(Department.branch_id, Department.name)).all()
    if request.method == "POST":
        username = request.form.get("username", "").lower().strip()
        password = request.form.get("password", "")
        full_name = request.form.get("full_name", "").strip()
        role = db.session.get(Role, request.form.get("role_id", type=int))
        branch = db.session.get(Branch, request.form.get("branch_id", type=int)) if request.form.get("branch_id", type=int) else None
        department = db.session.get(Department, request.form.get("department_id", type=int)) if request.form.get("department_id", type=int) else None
        if not username or not full_name or not role or len(password) < 12:
            flash("Username, full name, role, and a password of at least 12 characters are required.", "danger")
        elif db.session.scalar(db.select(User).where(User.username == username)):
            flash("Username already exists.", "danger")
        elif department and branch and department.branch_id != branch.id:
            flash("Department does not belong to the selected branch.", "danger")
        else:
            user = User(
                username=username,
                email=request.form.get("email", "").strip() or None,
                full_name=full_name,
                employee_id=request.form.get("employee_id", "").strip() or None,
                mobile=request.form.get("mobile", "").strip() or None,
                position=request.form.get("position", "").strip() or None,
                role=role,
                branch=branch,
                department=department,
                is_active=True,
                must_change_password=True,
            )
            user.set_password(password)
            db.session.add(user)
            try:
                db.session.flush()
                audit("USER_CREATED", "User", user.id, f"{user.username}; role={role.code}")
                db.session.commit()
                flash("User account created.", "success")
            except Exception:
                db.session.rollback()
                flash("The email or employee ID is already assigned to another user.", "danger")
        return redirect(url_for("admin.users"))

    search = request.args.get("q", "").strip()
    query = db.select(User)
    if search:
        like = f"%{search}%"
        query = query.where(or_(User.username.ilike(like), User.full_name.ilike(like), User.employee_id.ilike(like)))
    rows = db.session.scalars(query.order_by(User.full_name)).all()
    return render_template("admin/users.html", users=rows, roles=roles, branches=branches, departments=departments)


@bp.post("/users/<int:user_id>/update")
@permission_required("admin.users")
def user_update(user_id):
    user = db.get_or_404(User, user_id)
    role = db.session.get(Role, request.form.get("role_id", type=int))
    branch = db.session.get(Branch, request.form.get("branch_id", type=int)) if request.form.get("branch_id", type=int) else None
    department = db.session.get(Department, request.form.get("department_id", type=int)) if request.form.get("department_id", type=int) else None
    if not role or (department and branch and department.branch_id != branch.id):
        flash("Select a valid role, branch, and department combination.", "danger")
        return redirect(url_for("admin.users"))
    user.full_name = request.form.get("full_name", "").strip() or user.full_name
    user.email = request.form.get("email", "").strip() or None
    user.mobile = request.form.get("mobile", "").strip() or None
    user.position = request.form.get("position", "").strip() or None
    user.role = role
    user.branch = branch
    user.department = department
    password = request.form.get("new_password", "")
    if password:
        if len(password) < 12:
            flash("Replacement password must contain at least 12 characters.", "danger")
            return redirect(url_for("admin.users"))
        user.set_password(password)
        user.must_change_password = True
    audit("USER_UPDATED", "User", user.id, f"role={role.code}")
    try:
        db.session.commit()
        flash("User updated.", "success")
    except Exception:
        db.session.rollback()
        flash("The supplied email is already in use.", "danger")
    return redirect(url_for("admin.users"))


@bp.post("/users/<int:user_id>/toggle")
@permission_required("admin.users")
def user_toggle(user_id):
    user = db.get_or_404(User, user_id)
    if user.id == current_user.id:
        flash("You cannot deactivate your own account.", "danger")
    else:
        user.is_active = not user.is_active
        audit("USER_STATUS_CHANGED", "User", user.id, f"Active={user.is_active}")
        db.session.commit()
        flash("User status updated.", "success")
    return redirect(url_for("admin.users"))


@bp.get("/roles")
@permission_required("admin.roles")
def roles():
    rows = db.session.scalars(db.select(Role).order_by(Role.name)).all()
    permissions = db.session.scalars(db.select(Permission).order_by(Permission.code)).all()
    return render_template("admin/roles.html", roles=rows, permissions=permissions)


@bp.post("/roles/<int:role_id>")
@permission_required("admin.roles")
def role_update(role_id):
    role = db.get_or_404(Role, role_id)
    scope = request.form.get("scope")
    permission_ids = request.form.getlist("permission_ids", type=int)
    if scope not in {"own", "department", "branch", "all"}:
        flash("Invalid visibility scope.", "danger")
        return redirect(url_for("admin.roles"))
    role.scope = scope
    role.name = request.form.get("name", "").strip() or role.name
    role.permissions = db.session.scalars(db.select(Permission).where(Permission.id.in_(permission_ids or [-1]))).all()
    audit("ROLE_UPDATED", "Role", role.id, f"scope={scope}; permissions={len(role.permissions)}")
    db.session.commit()
    flash("Role permissions updated.", "success")
    return redirect(url_for("admin.roles"))


@bp.get("/audit")
@permission_required("admin.audit")
def audit_logs():
    search = request.args.get("q", "").strip()
    query = db.select(AuditLog)
    if search:
        like = f"%{search}%"
        query = query.where(or_(AuditLog.action.ilike(like), AuditLog.entity_type.ilike(like), AuditLog.details.ilike(like)))
    page = max(request.args.get("page", 1, type=int), 1)
    per_page = 40
    total = db.session.scalar(db.select(func.count()).select_from(query.order_by(None).subquery())) or 0
    rows = db.session.scalars(query.order_by(AuditLog.created_at.desc()).offset((page - 1) * per_page).limit(per_page)).all()
    return render_template("admin/audit.html", logs=rows, total=total, page=page, per_page=per_page)
