# common_utils.py
from __future__ import annotations

import json
from datetime import datetime
from typing import Optional

from flask import current_app
from extensions import db
from models import SystemSetting, ProductiveHoursRule, Employee
from functools import wraps
from flask import redirect, url_for, request, flash
from flask_login import current_user
from models import Employee

# ---------- Generic key/value settings ----------

def get_setting(key: str, default=None, cast=None):
    """
    Unified settings loader:
      1. Try database (SystemSetting table)
      2. Fallback to config.py (current_app.config)
      3. If missing, fallback to provided default

    Supports type casting via: cast=bool, int, float
    """
    row = None
    try:
        row = SystemSetting.query.filter_by(key=key).first()
    except Exception:
        # Happens during migrations / very early boot
        row = None

    if row and row.value not in (None, ""):
        value = row.value
    else:
        # Fallback to config
        try:
            # Config keys are typically upper-case; we allow both
            value = (
                current_app.config.get(key)
                or current_app.config.get(key.upper(), default)
            )
        except Exception:
            value = default

    # ---- casting ----
    if cast is bool:
        return str(value).lower() in ("1", "true", "yes", "on")

    if cast is int:
        try:
            return int(value)
        except Exception:
            return default

    if cast is float:
        try:
            return float(value)
        except Exception:
            return default

    return value


def set_setting(key: str, value: str, updated_by_id: Optional[int] = None):
    """
    Helper to update SystemSetting from code if needed.
    """
    s = SystemSetting.query.filter_by(key=key).first()
    if not s:
        s = SystemSetting(key=key)
        db.session.add(s)

    s.value = value
    s.updated_at = datetime.utcnow()
    s.updated_by_id = updated_by_id
    db.session.commit()
    return s


# ---------- Email config from DB (email_settings JSON) ----------

def apply_email_settings_from_db(app):
    """
    Load email settings from SystemSetting(key='email_settings')
    and override app.config MAIL_* values.

    Expects value like:
    {
      "smtp_server": "smtp.gmail.com",
      "smtp_port": 587,
      "use_tls": true,
      "use_ssl": false,
      "username": "tools@upila.com",
      "password": "xxx",
      "from_name": "UPILA HR",
      "from_email": "tools@upila.com"
    }
    """
    from sqlalchemy.exc import OperationalError

    with app.app_context():
        try:
            setting = SystemSetting.query.filter_by(key="email_settings").first()
        except OperationalError:
            app.logger.warning("SystemSetting table not ready; using config.py email settings.")
            return

        if not setting or not setting.value:
            app.logger.info("No email_settings row found – using config.py values.")
            return

        try:
            data = setting.value
            if isinstance(data, str):
                data = json.loads(data)
        except Exception as e:
            app.logger.exception(f"Invalid email_settings JSON: {e}")
            return

        # Map JSON → Flask-Mail config
        app.config["MAIL_SERVER"] = data.get("smtp_server", app.config.get("MAIL_SERVER"))
        app.config["MAIL_PORT"] = data.get("smtp_port", app.config.get("MAIL_PORT"))
        app.config["MAIL_USE_TLS"] = data.get("use_tls", app.config.get("MAIL_USE_TLS"))
        app.config["MAIL_USE_SSL"] = data.get("use_ssl", app.config.get("MAIL_USE_SSL"))
        app.config["MAIL_USERNAME"] = data.get("username", app.config.get("MAIL_USERNAME"))
        app.config["MAIL_PASSWORD"] = data.get("password", app.config.get("MAIL_PASSWORD"))

        # Default sender
        from_name = (
            data.get("from_name")
            or app.config.get("MAIL_FROM")
            or (app.config.get("MAIL_DEFAULT_SENDER", ("", ""))[0] if app.config.get("MAIL_DEFAULT_SENDER") else "")
        )
        from_email = (
            data.get("from_email")
            or app.config.get("MAIL_FROM")
            or (app.config.get("MAIL_DEFAULT_SENDER", ("", ""))[-1] if app.config.get("MAIL_DEFAULT_SENDER") else "")
        )

        app.config["MAIL_DEFAULT_SENDER"] = (from_name, from_email)

        app.logger.info(f"Email settings loaded from DB: {from_name} <{from_email}>")


# ---------- Productive hours rule helper ----------

def get_required_hours_for_employee(emp: Employee, default_hours: float = 7.0) -> float:
    """
    Resolve required hours based on ProductiveHoursRule:
    priority: employee > designation > department > company(default)
    """
    try:
        # Employee specific
        rule = ProductiveHoursRule.query.filter_by(
            scope="employee", employee_id=emp.id
        ).first()
        if rule and rule.required_hours:
            return float(rule.required_hours)

        # Designation
        if emp.designation_id:
            rule = ProductiveHoursRule.query.filter_by(
                scope="designation", designation_id=emp.designation_id
            ).first()
            if rule and rule.required_hours:
                return float(rule.required_hours)

        # Department
        if emp.department_id:
            rule = ProductiveHoursRule.query.filter_by(
                scope="department", department_id=emp.department_id
            ).first()
            if rule and rule.required_hours:
                return float(rule.required_hours)

        # Company default
        rule = ProductiveHoursRule.query.filter_by(scope="company").first()
        if rule and rule.required_hours:
            return float(rule.required_hours)

    except Exception:
        db.session.rollback()

    return float(default_hours)

def employee_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Ensure the logged-in user is actually an Employee instance
        if not isinstance(current_user._get_current_object(), Employee):
            flash('❌ Please log in as an employee.', 'danger')
            return redirect(url_for('employee.employee_login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function