# app.py
from flask import Flask, render_template, redirect, url_for, current_app, send_from_directory, abort
from werkzeug.utils import safe_join
import os
from datetime import datetime
from config import Config
from extensions import db, bcrypt, login_manager, mail
from routes.master import master_bp
from flask_login import current_user, login_required
from models import Notification
from sqlalchemy import or_, desc
from werkzeug.middleware.proxy_fix import ProxyFix
import click
from cli.attendance_cli import run_daily_desktime_sync
from common_utils import apply_email_settings_from_db


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    # ---------- Core config ----------
    app.config.setdefault('ALLOWED_DOC_EXTENSIONS', {'pdf', 'png', 'jpg', 'jpeg', 'webp', 'doc', 'docx'})
    app.config.setdefault('MAX_CONTENT_LENGTH', 20 * 1024 * 1024)  # 20 MB
    app.config.setdefault('DOCUMENTS_UPLOAD_FOLDER', os.path.join(app.root_path, 'uploads', 'employee_docs'))
    os.makedirs(app.config['DOCUMENTS_UPLOAD_FOLDER'], exist_ok=True)

    app.config.setdefault('UPLOAD_FOLDER', os.path.join(app.root_path, 'uploads'))

    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
    app.config.setdefault("PREFERRED_URL_SCHEME", "https")

    @app.route("/")
    def index():
        return redirect(url_for("employee.employee_login"))

    # ---------- Initialize extensions ----------
    db.init_app(app)
    bcrypt.init_app(app)
    login_manager.init_app(app)
    login_manager.login_view = "auth.login"
    mail.init_app(app)

    # ✅ Override MAIL_* from SystemSetting.email_settings (if present)
    apply_email_settings_from_db(app)

    # ---------- File serving ----------
    @app.route("/uploads/<path:subpath>")
    @login_required
    def serve_upload(subpath: str):
        # Quick access control: owner or HR/Admin
        parts = subpath.split("/", 1)
        if parts and parts[0].isdigit():
            owner_id = int(parts[0])
            role_name = (getattr(getattr(current_user, "role", None), "name", "") or "").lower()
            is_hr_admin = role_name in ("hr", "admin", "hr admin")
            if current_user.id != owner_id and not is_hr_admin:
                abort(403)

        root = current_app.config["UPLOAD_FOLDER"]
        full = safe_join(root, subpath)
        if not full or not os.path.isfile(full):
            abort(404)

        # Stream with ETag/range support; no caching by default
        return send_from_directory(root, subpath, conditional=True, max_age=0)

    # ---------- Theme / branding ----------
    from models import BrandingSetting
    from sqlalchemy.exc import OperationalError

    @app.context_processor
    def inject_theme():
        defaults = {
            "sidebar_bg": current_app.config.get("BRAND_SIDEBAR_COLOR", "#0054A6"),
            "topbar_bg": current_app.config.get("BRAND_TOPBAR_COLOR", "#0054A6"),
            "accent": current_app.config.get("BRAND_ACCENT_COLOR", "#F47C20"),
            "logo_url": current_app.config.get(
                "BRAND_LOGO_URL", url_for("static", filename="images/logo.png")
            ),
            "version": int(datetime.utcnow().timestamp()),
        }
        try:
            bs = BrandingSetting.query.get(1)
            if not bs:
                return dict(theme=defaults)
            return dict(
                theme={
                    "sidebar_bg": bs.sidebar_bg or defaults["sidebar_bg"],
                    "topbar_bg": bs.topbar_bg or defaults["topbar_bg"],
                    "accent": bs.accent or defaults["accent"],
                    "logo_url": bs.logo_url or defaults["logo_url"],
                    "version": int(bs.updated_at.timestamp()) if bs.updated_at else defaults["version"],
                }
            )
        except OperationalError:
            return dict(theme=defaults)

    # ---------- Sidebar menus ----------
    @app.context_processor
    def inject_sidebar_links():
        from models import Menu, RoleAccess, UserAccess, SubMenu

        if not current_user.is_authenticated:
            return {}

        role_name = (getattr(getattr(current_user, "role", None), "name", "") or "").strip().lower()

        role_ids = [ra.submenu_id for ra in RoleAccess.query.filter_by(role=role_name).all()]
        user_ids = [ua.submenu_id for ua in UserAccess.query.filter_by(user_id=current_user.id).all()]

        allowed_ids = set(role_ids) | set(user_ids)

        if role_name == "admin" and not allowed_ids:
            allowed_ids = {sid for (sid,) in db.session.query(SubMenu.id).all()}

        menus = (
            Menu.query.options(db.joinedload(Menu.submenus))
            .order_by(Menu.order.asc(), Menu.name.asc())
            .all()
        )

        allowed_menus = []
        for m in menus:
            subs = [
                s
                for s in sorted(
                    m.submenus,
                    key=lambda x: ((x.order or 0), (x.name or "").lower()),
                )
                if s.id in allowed_ids
            ]
            if subs:
                allowed_menus.append(
                    {
                        "name": m.name,
                        "icon": m.icon,
                        "submenus": subs,
                    }
                )

        return dict(sidebar_menus=allowed_menus)

    # ---------- Notifications ----------
    @app.context_processor
    def inject_notifications():
        try:
            if not current_user.is_authenticated:
                return dict(notifications_unread_count=0, notifications=[])

            employee_id = getattr(current_user, "id", None)
            if not employee_id:
                return dict(notifications_unread_count=0, notifications=[])

            base_q = Notification.query.filter(
                or_(
                    Notification.employee_id == employee_id,
                    Notification.employee_id.is_(None),
                )
            )

            unread_count = base_q.filter(Notification.is_read.is_(False)).count()
            recent = base_q.order_by(desc(Notification.created_at)).limit(10).all()

            return dict(
                notifications_unread_count=unread_count,
                notifications=recent,
            )
        except Exception:
            db.session.rollback()
            return dict(notifications_unread_count=0, notifications=[])

    # ---------- CLI: desktime-daily-sync ----------
    @app.cli.command("desktime-daily-sync")
    @click.option(
        "--date",
        "date_str",
        default=None,
        help="Optional date in YYYY-MM-DD (default = yesterday)",
    )
    def desktime_daily_sync_cmd(date_str):
        """Run daily DeskTime sync for all employees with a DeskTime ID."""
        from datetime import datetime as dt, date as ddate

        if date_str:
            try:
                target_date = dt.strptime(date_str, "%Y-%m-%d").date()
            except ValueError:
                click.echo("Invalid --date. Use YYYY-MM-DD.")
                return
        else:
            target_date = None  # will default to yesterday in helper

        log_entry = run_daily_desktime_sync(target_date)
        click.echo(f"DeskTime sync finished with status: {log_entry.status}")

    # ---------- Error handlers ----------
    @app.errorhandler(403)
    def forbidden(e):
        return render_template("403.html"), 403

    # ---------- Register blueprints & CLI commands ----------
    with app.app_context():
        from routes.auth import auth_bp
        from routes.employee import employee_bp
        from routes.dashboard import dashboard_bp
        from routes.broadcast import broadcast_bp
        from routes.leave import leave_bp
        from cli.leave_cli import auto_credit_leaves
        from routes.attendance import attendance_bp
        from routes.payroll import payroll_bp
        from cli.attendance_cli import process_attendance
        from routes.desktime_logs import desktime_logs_bp
        from admin import admin_bp
        from routes.documents import bp as documents_bp
        from routes.attendance_summary import attendance_summary_bp
        from routes.email_actions import email_actions_bp
        from routes.admin_email_logs import admin_email_logs_bp
        from routes.exit import exit_bp
        from routes.exit_lists import exit_list_bp
        from routes.notifications import notifications_bp
        from routes.feedback_admin import feedback_admin_bp
        from routes.feedback_employee import feedback_emp_bp
        from routes.feedback_reports import feedback_reports_bp
        from routes.travel import tr_bp
        from routes.docs import docs_bp
        from commands.bootstrap import bootstrap_empty
        from routes.custom_dashboard import dashboard_bp as dashbuild
        from routes.assets import assets_bp
        from routes.biometric_views import biometric_bp

        app.register_blueprint(biometric_bp)
        app.register_blueprint(assets_bp)
        app.register_blueprint(dashbuild)
        app.register_blueprint(docs_bp, url_prefix="/docs")
        app.register_blueprint(tr_bp)
        app.register_blueprint(feedback_reports_bp)
        app.register_blueprint(feedback_admin_bp)
        app.register_blueprint(feedback_emp_bp)
        app.register_blueprint(notifications_bp)
        app.register_blueprint(exit_list_bp)
        app.register_blueprint(exit_bp)
        app.register_blueprint(admin_email_logs_bp)
        app.register_blueprint(email_actions_bp)
        app.register_blueprint(documents_bp, url_prefix="/documents")
        app.register_blueprint(admin_bp, url_prefix="/admin")
        app.register_blueprint(desktime_logs_bp)
        app.register_blueprint(leave_bp)
        app.register_blueprint(auth_bp, url_prefix="/auth")
        app.register_blueprint(employee_bp)
        app.register_blueprint(dashboard_bp)
        app.register_blueprint(master_bp)
        app.register_blueprint(broadcast_bp, url_prefix="/broadcast")
        app.register_blueprint(attendance_bp)
        app.register_blueprint(payroll_bp, url_prefix="/payroll")
        app.register_blueprint(attendance_summary_bp)

        app.cli.add_command(auto_credit_leaves)
        app.cli.add_command(process_attendance)
        app.cli.add_command(bootstrap_empty)

    return app


app = create_app()

if __name__ == "__main__":
   app.run(port=5021, debug=True)