import os
import secrets
import time

from flask import Flask, render_template, send_from_directory, request, jsonify, flash, redirect, url_for
from flask_login import current_user

from config import CONFIG_MAP, APP_NAME
from app.extensions import db, login_manager, csrf, limiter
from app.services import http_perf, jalali
from app.services.listing import show_all_url
from app.services.permissions import allowed_page_keys, can_access, page_label


def _persistent_secret_key(instance_path: str) -> str:
    """اگر SECRET_KEY روی هاست ست نشده باشه، به‌جای یک کلید ثابت و ناامن، یک کلید تصادفی
    یک‌بار ساخته و در instance/secret_key نگه داشته می‌شه (با ری‌استارت عوض نمی‌شه)."""
    path = os.path.join(instance_path, "secret_key")
    for _attempt in range(5):
        try:
            with open(path, encoding="utf-8") as f:
                key = f.read().strip()
            if key:
                return key
        except FileNotFoundError:
            try:
                fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
            except FileExistsError:
                continue  # پروسه‌ی دیگه‌ای همین الان ساختش؛ دوباره بخون
            key = secrets.token_hex(32)
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                f.write(key)
            return key
        time.sleep(0.1)
    raise RuntimeError(f"کلید امنیتی از {path} خوانده نشد")


def _format_grams(value) -> str:
    """وزن به گرم برای نمایش: 320 -> «320»، 12.5 -> «12.5»."""
    try:
        return f"{float(value or 0):,.3f}".rstrip("0").rstrip(".")
    except (TypeError, ValueError):
        return "-"


def create_app(config_name=None):
    app = Flask(__name__)
    config_name = config_name or os.environ.get("FLASK_CONFIG", "development")
    app.config.from_object(CONFIG_MAP[config_name])

    app.config["LOCAL_PHOTO_DIR"].mkdir(parents=True, exist_ok=True)
    (app.instance_path and os.makedirs(app.instance_path, exist_ok=True))
    if not app.config.get("SECRET_KEY"):
        app.config["SECRET_KEY"] = _persistent_secret_key(app.instance_path)

    db.init_app(app)
    if os.environ.get("FLASK_RUN_FROM_CLI") or os.environ.get("ENABLE_MIGRATIONS"):
        # alembic فقط برای دستور «flask db» لازمه و حدود ۱۰٪ به زمان بالا آمدن برنامه اضافه می‌کنه؛
        # وقتی سایت داره سرو می‌شه اصلاً لود نمی‌شه. (Flask این متغیر رو خودش قبل از ساخت اپ ست می‌کنه.)
        from flask_migrate import Migrate

        Migrate(app, db)
    login_manager.init_app(app)
    login_manager.login_view = "auth.login"
    login_manager.login_message = None
    csrf.init_app(app)
    limiter.init_app(app)

    from app.models.user import User
    from app.services import customer_service

    @login_manager.user_loader
    def load_user(user_id):
        return db.session.get(User, int(user_id))

    from app.blueprints.auth.routes import bp as auth_bp
    from app.blueprints.dashboard.routes import bp as dashboard_bp
    from app.blueprints.accounts.routes import bp as accounts_bp
    from app.blueprints.calls.routes import bp as calls_bp
    from app.blueprints.tag_admin.routes import bp as tag_admin_bp
    from app.blueprints.settings.routes import bp as settings_bp
    from app.blueprints.reports.routes import bp as reports_bp
    from app.blueprints.tasks.routes import bp as tasks_bp
    from app.blueprints.tasks_board.routes import bp as tasks_board_bp
    from app.blueprints.customer_account.routes import bp as customer_account_bp
    from app.blueprints.customer_profile.routes import bp as customer_profile_bp
    from app.blueprints.weight.routes import bp as weight_bp

    app.register_blueprint(auth_bp)
    app.register_blueprint(dashboard_bp)
    app.register_blueprint(accounts_bp)
    app.register_blueprint(calls_bp)
    app.register_blueprint(tag_admin_bp)
    app.register_blueprint(settings_bp)
    app.register_blueprint(reports_bp)
    app.register_blueprint(tasks_bp)
    app.register_blueprint(tasks_board_bp)
    app.register_blueprint(customer_account_bp)
    app.register_blueprint(customer_profile_bp)
    app.register_blueprint(weight_bp)

    app.add_template_filter(_format_grams, "grams")

    # کش فایل‌های استاتیک + فشرده‌سازی پاسخ‌ها (هاست خودش هیچ‌کدام را انجام نمی‌دهد)
    http_perf.register(app)

    @app.context_processor
    def inject_globals():
        return {
            "APP_NAME": APP_NAME,
            "jalali": jalali,
            "can_access": can_access,
            "allowed_page_keys": allowed_page_keys,
            "page_label": page_label,
            "show_all_url": show_all_url,
        }

    @app.before_request
    def refresh_customer_statuses():
        # درخواست فایل‌های استاتیک و باندل‌ها نباید به session دست بزنه: هر دست‌زدنی باعث می‌شه
        # Flask هدر «Vary: Cookie» اضافه کنه و اون‌وقت با هر تغییر کوکی (ورود، خروج، حتی یک پیام
        # موفقیت) کشِ CSS/JS مرورگر باطل و دوباره دانلود می‌شه.
        if not request.endpoint or request.path.startswith("/static/"):
            return
        # «خواب» شدن مشتری با گذشت زمان اتفاق می‌افته؛ حداکثر هر ۳۰ دقیقه یک‌بار دوباره حساب می‌شه
        if current_user.is_authenticated:
            customer_service.refresh_statuses_if_due()

    @app.route("/sw.js")
    def service_worker():
        # از ریشه سرو می‌شود (نه /static/) تا scope کل سایت رو پوشش بده
        response = send_from_directory(app.static_folder, "sw.js", mimetype="application/javascript")
        response.headers["Service-Worker-Allowed"] = "/"
        return response

    @app.errorhandler(403)
    def forbidden(_e):
        return render_template("errors/403.html"), 403

    @app.errorhandler(404)
    def not_found(_e):
        return render_template("errors/404.html"), 404

    @app.errorhandler(413)
    def too_large(_e):
        message = "حجم فایل بیشتر از حد مجاز (۸ مگابایت) است"
        if request.path.startswith("/customers/import"):
            return jsonify({"error": message}), 413
        flash(message, "error")
        return redirect(request.referrer or url_for("dashboard.index"))

    @app.errorhandler(500)
    def server_error(_e):
        db.session.rollback()
        return render_template("errors/500.html"), 500

    return app
