# app.py
from flask import Flask, app, render_template, redirect, url_for, current_app, send_from_directory, abort
from werkzeug.utils import safe_join
import os
from datetime import datetime, date
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, DemoCompany, Employee
from sqlalchemy import or_, desc
from werkzeug.middleware.proxy_fix import ProxyFix
import click
from flask.cli import with_appcontext

# CLI imports – IMPORTANT: import here, once
from cli.attendance_cli import register_cli, run_daily_desktime_sync, process_attendance
from cli.biometric_cli import daily_biometrics
from utils.birthday_reminders import send_birthday_reminders

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("/uploads/documents/<path:filename>")
    def uploaded_document(filename):
        """
        Serve files from uploads/documents folder, e.g.
        /uploads/documents/self_punch/3/xxxx.png
        """
        upload_root = os.path.join(
            app.root_path,
            app.config.get("UPLOAD_FOLDER", "uploads/documents")
        )
        return send_from_directory(upload_root, filename)

    @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)

    # ---------- CLI registrations (global) ----------
    # Register the grouped CLI commands from cli/attendance_cli.py
    register_cli(app)                            # desktime-sync, biometric-sync, attendance-run, daily-attendance
    # Legacy / extra commands
    app.cli.add_command(process_attendance)      # process-attendance
    app.cli.add_command(daily_biometrics)        # daily-biometrics

    # ---------- 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

    import calendar

    @app.template_filter('month_name')
    def month_name_filter(month_number):
        try:
            return calendar.month_name[int(month_number)]
        except:
            return ""

    @app.context_processor
    def inject_profile_completion():
        if not current_user.is_authenticated:
            return {}

        # Only enforce for employees (skip admins if you want)
        role_name = (getattr(getattr(current_user, "role", None), "name", "") or "").strip().lower()

        # If you want admins/hr to also fill, remove this condition
        # if role_name in ("admin", "hr", "hr admin"):
        #     return dict(profile_needs_update=False, profile_missing_fields=[], profile_missing_labels=[])

        mandatory = [
            ("dob", "Date of Birth"),
            ("gender", "Gender"),
            ("marital_status", "Marital Status"),
            ("father_spouse_name", "Father/Spouse Name"),

            ("emergency_contact_name", "Emergency Contact Name"),
            ("emergency_contact_number", "Emergency Contact Number"),

            ("address", "Permanent Address"),
            ("temporary_address", "Temporary Address"),

            ("aadhar_name", "Name on Aadhaar"),
            ("aadhar_number", "Aadhaar Number"),

            ("pan_name", "Name on PAN"),
            ("pan_number", "PAN Number"),

            ("bank_account_name", "Name in Bank Account"),
            ("bank_account_number", "Bank Account Number"),
            ("ifsc_code", "IFSC Code"),
            ("bank_name", "Bank Name"),
        ]

        missing_fields = []
        missing_labels = []

        for field, label in mandatory:
            val = getattr(current_user, field, None)

            # Treat empty strings as missing
            if val is None:
                missing_fields.append(field)
                missing_labels.append(label)
            elif isinstance(val, str) and not val.strip():
                missing_fields.append(field)
                missing_labels.append(label)

        return dict(
            profile_needs_update=len(missing_fields) > 0,
            profile_missing_fields=missing_fields,
            profile_missing_labels=missing_labels,
            profile_missing_map=dict(zip(missing_fields, missing_labels)),
        )
    @app.cli.command('cleanup-expired-demos')
    @with_appcontext
    def cleanup_expired_demos():
        """Delete expired demo companies, their demo accounts and demo employees."""
        today = date.today()
        expired_companies = DemoCompany.query.filter(
            DemoCompany.expires_on.isnot(None),
            DemoCompany.expires_on < today
        ).all()

        deleted_emps = 0
        deleted_companies = len(expired_companies)

        for company in expired_companies:
            for acc in company.demo_accounts:
                if acc.employee_id:
                    emp = Employee.query.get(acc.employee_id)
                    if emp:
                        try:
                            db.session.delete(emp)
                            deleted_emps += 1
                        except Exception:
                            # If there are FK constraints, just deactivate instead
                            emp.status = 'inactive'
                db.session.delete(acc)

            db.session.delete(company)

        db.session.commit()
        click.echo(f"Deleted {deleted_companies} demo companies and {deleted_emps} demo employees.")

    @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 = db.session.get(BrandingSetting, 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)

    @app.cli.command("birthday-reminders")
    def birthday_reminders_cmd():
        sent, skipped = send_birthday_reminders(lead_days=2)
        print(f"Birthday reminders sent={sent}, skipped={skipped}")
        
    # ---------- CLI: desktime-daily-sync (single-day batch) ----------
    @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

        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}")

    # ---------- 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=[])

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

    # ---------- Register blueprints & remaining CLI ----------
    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 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
        from routes.attendance_admin import attendance_admin_bp
        from routes.recruitment import recruitment_bp
        from routes.loan import loan_bp
        from routes.birthday_tools import birthday_tools_bp
        from routes.appraisal import appraisal_bp
        from routes.team_attendance import team_attendance_bp
        from routes.team_salary import team_salary_bp
        from routes.api import api_bp
        from routes.assistant import assistant_bp
        from routes.institute_access import institute_access_bp
        from routes.incentives import incentive_bp
        from routes.bonus import bonus_bp
        from routes.salary_slip_template import salary_slip_template_bp
        from routes.demo_public import demo_public_bp

        app.register_blueprint(demo_public_bp)
        app.register_blueprint(salary_slip_template_bp)
        app.register_blueprint(bonus_bp)
        app.register_blueprint(incentive_bp)
        app.register_blueprint(institute_access_bp)
        app.register_blueprint(assistant_bp)
        app.register_blueprint(api_bp)
        app.register_blueprint(team_salary_bp)
        app.register_blueprint(team_attendance_bp)
        app.register_blueprint(appraisal_bp)
        app.register_blueprint(birthday_tools_bp)    
        app.register_blueprint(loan_bp)
        app.register_blueprint(recruitment_bp)
        app.register_blueprint(attendance_admin_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)

        # Extra CLI from other modules
        app.cli.add_command(auto_credit_leaves)
        app.cli.add_command(bootstrap_empty)

    return app


app = create_app()

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