from flask import Blueprint, render_template, request, redirect, url_for, flash, abort
from flask_login import login_user, logout_user, login_required, current_user
from urllib.parse import urlparse, urljoin
from datetime import datetime
from functools import wraps

from extensions import db
from models import Employee, Role, RoleAccess, SubMenu, EmployeeSalaryStructures

auth_bp = Blueprint('auth', __name__)

# ---------- helpers ----------

BUILTIN_ROLES = {'admin'}  # protect these from delete/rename if you like

def _is_safe_url(target: str) -> bool:
    if not target:
        return False
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urljoin(request.host_url, target))
    return (test_url.scheme in ("http", "https")
            and ref_url.netloc == test_url.netloc)

def _norm_role(name: str) -> str:
    return (name or "").strip().lower()

def _has_endpoint_access(role_name: str, endpoint: str) -> bool:
    """Check if a role has access to a given Flask endpoint via RoleAccess/SubMenu."""
    if not role_name or not endpoint:
        return False
    role_name = _norm_role(role_name)
    # Admin short-circuit
    if role_name == 'admin':
        return True
    sm = SubMenu.query.filter_by(endpoint=endpoint).first()
    if not sm:
        # If no submenu is defined for endpoint, default to deny (safer)
        return False
    return RoleAccess.query.filter_by(role=role_name, submenu_id=sm.id).first() is not None

def roles_required(*role_names):
    want = {_norm_role(r) for r in role_names if r}
    def wrapper(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            if not current_user.is_authenticated or not current_user.role:
                flash("Please log in to continue.", "warning")
                return redirect(url_for('auth.login'))
            have = _norm_role(current_user.role.name)
            if have in want:
                return f(*args, **kwargs)
            flash("You do not have permission to access this page.", "danger")
            return redirect(url_for('auth.forbidden'))
        return decorated
    return wrapper

def permission_required(endpoint_name: str):
    """Use this on sensitive routes that should obey Menu/Submenu access."""
    def wrapper(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            if not current_user.is_authenticated or not current_user.role:
                flash("Please log in to continue.", "warning")
                return redirect(url_for('auth.login'))
            role_name = _norm_role(current_user.role.name)
            if _has_endpoint_access(role_name, endpoint_name):
                return f(*args, **kwargs)
            flash("You do not have permission to access this feature.", "danger")
            return redirect(url_for('auth.forbidden'))
        return decorated
    return wrapper

# ---------- auth ----------

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard_bp.index'))

    error = None
    if request.method == 'POST':
        username_or_email = (request.form.get('username') or "").strip()
        password = request.form.get('password') or ""
        ip = request.headers.get('X-Forwarded-For', request.remote_addr)

        employee = Employee.query.filter(
            (Employee.email == username_or_email) | (Employee.employee_code == username_or_email)
        ).first()

        if employee and employee.check_password(password):
            if not employee.role:
                flash("❌ Role not assigned. Please contact HR.", "danger")
                return redirect(url_for('auth.login'))

            role_name = _norm_role(employee.role.name)

            # Bypass salary assignment check for privileged roles
            bypass_roles = {'admin', 'hr', 'hr admin', 'hradmin'}
            if role_name not in bypass_roles:
                has_salary = EmployeeSalaryStructures.query.filter(
                    EmployeeSalaryStructures.employee_id == employee.id,
                    EmployeeSalaryStructures.amount > 0
                ).first()
                if not has_salary:
                    flash("❌ You cannot log in until your salary structure is assigned. Please contact HR.", "danger")
                    return redirect(url_for('auth.login'))

            login_user(employee, remember=('remember' in request.form))
            employee.last_login = datetime.utcnow()
            employee.last_ip = ip
            db.session.commit()

            nxt = request.args.get('next')
            if nxt and _is_safe_url(nxt):
                return redirect(nxt)
            return redirect(url_for('dashboard_bp.index'))

        error = '❌ Invalid credentials'
        flash(error, 'danger')

    return render_template('login.html')

@auth_bp.route('/logout')
@login_required
def logout():
    current_user.last_logout = datetime.utcnow()
    db.session.commit()
    logout_user()
    flash('✅ You have been logged out.', 'success')
    return redirect(url_for('employee.employee_login'))

# ---------- roles CRUD ----------

@auth_bp.route('/manage-roles', methods=['GET', 'POST'])
@roles_required('admin')
def manage_roles():
    """Create new roles and list existing ones.
       Bonus: assign roles to users right here."""
    if request.method == 'POST':
        # New role
        role_name = _norm_role(request.form.get('role_name'))
        if not role_name:
            flash('Role name is required.', 'danger')
            return redirect(url_for('auth.manage_roles'))

        if Role.query.filter_by(name=role_name).first():
            flash('Role already exists.', 'warning')
            return redirect(url_for('auth.manage_roles'))

        db.session.add(Role(name=role_name))
        db.session.commit()
        flash(f'Role "{role_name}" added successfully.', 'success')
        return redirect(url_for('auth.manage_roles'))

    roles = Role.query.order_by(Role.name).all()
    # counts for UI
    role_counts = {r.id: len(r.employees or []) for r in roles}
    employees = Employee.query.order_by(Employee.full_name).all()
    return render_template('manage_roles.html', roles=roles, role_counts=role_counts, employees=employees)

@auth_bp.route('/assign-role', methods=['POST'])
@roles_required('admin')
def assign_role():
    emp_id = request.form.get('employee_id', type=int)
    role_id = request.form.get('role_id', type=int)
    emp = Employee.query.get_or_404(emp_id)
    role = Role.query.get_or_404(role_id)

    emp.role = role
    db.session.commit()
    flash(f'Assigned role "{role.name}" to {emp.full_name}.', 'success')
    return redirect(url_for('auth.manage_roles'))

@auth_bp.route('/delete-role/<int:role_id>', methods=['POST'])
@roles_required('admin')
def delete_role(role_id):
    role = Role.query.get_or_404(role_id)
    # protect built-in admin
    if _norm_role(role.name) in BUILTIN_ROLES:
        flash('Cannot delete the built-in "admin" role.', 'danger')
        return redirect(url_for('auth.manage_roles'))

    if role.employees:
        flash('Cannot delete role assigned to employees.', 'danger')
        return redirect(url_for('auth.manage_roles'))

    db.session.delete(role)
    db.session.commit()
    flash('Role deleted successfully.', 'success')
    return redirect(url_for('auth.manage_roles'))

@auth_bp.route('/forbidden')
def forbidden():
    return render_template('403.html'), 403

