from __future__ import annotations
import os
import time
import shutil
import zipfile
import logging
from pathlib import Path
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, session, send_from_directory, send_file
from flask_login import login_required, current_user
from concurrent.futures import ThreadPoolExecutor
from watchdog.observers import Observer
from natsort import natsorted

from backend.services.ip_processor import (
    save_ip_addresses,
    process_ips_concurrently,
    get_progress,
    on_complete,
)
from backend.services.watchdog_handler import FileCreatedHandler
from config import Config

main_bp = Blueprint("main", __name__)
executor = ThreadPoolExecutor(max_workers=Config.MAX_WORKERS)


def register_main_routes(app, socketio):
    app.register_blueprint(main_bp)

    @app.context_processor
    def inject_user():
        return dict(current_user=current_user)

    @app.before_request
    def make_session_permanent():
        session.permanent = True
        if current_user.is_authenticated:
            session.modified = True


@main_bp.route("/")
@main_bp.route("/index", methods=["GET"])
@login_required
def index():
    script_dir = Path(Config.SCRIPT_FOLDER)
    xml_dir = Path(Config.XML_FOLDER)
    info_dir = Path(Config.IDRAC_INFO_FOLDER)
    backup_dir = Path(Config.BACKUP_FOLDER)

    # 1. 스크립트 목록 조회 및 카테고리 분류
    all_scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
    all_scripts = natsorted(all_scripts)

    grouped_scripts = {}
    for script in all_scripts:
        upper = script.upper()
        category = "General"
        if upper.startswith("GPU"):
            category = "GPU"
        elif upper.startswith("LOM"):
            category = "LOM"
        elif upper.startswith("TYPE") or upper.startswith("XE"):
            category = "Server Models"
        elif "MAC" in upper:
            category = "MAC Info"
        elif "GUID" in upper:
            category = "GUID Info"
        elif "SET_" in upper or "CONFIG" in upper:
            category = "Configuration"
        
        if category not in grouped_scripts:
            grouped_scripts[category] = []
        grouped_scripts[category].append(script)

    # 카테고리 정렬 (General은 마지막에)
    sorted_categories = sorted(grouped_scripts.keys())
    if "General" in sorted_categories:
        sorted_categories.remove("General")
        sorted_categories.append("General")

    grouped_scripts_sorted = {k: grouped_scripts[k] for k in sorted_categories}

    # 2. XML 파일 목록
    xml_files = [f.name for f in xml_dir.glob("*.xml")]

    # 3. 페이지네이션 및 파일 목록
    page = int(request.args.get("page", 1))
    info_files = [f.name for f in info_dir.glob("*") if f.is_file()]
    info_files = natsorted(info_files)

    start = (page - 1) * Config.FILES_PER_PAGE
    end = start + Config.FILES_PER_PAGE
    files_to_display = [{"name": Path(f).stem, "file": f} for f in info_files[start:end]]

    total_pages = (len(info_files) + Config.FILES_PER_PAGE - 1) // Config.FILES_PER_PAGE

    start_page = ((page - 1) // 10) * 10 + 1
    end_page = min(start_page + 9, total_pages)

    # 4. 백업 폴더 목록
    backup_dirs = [d for d in backup_dir.iterdir() if d.is_dir()]
    backup_dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)

    backup_page = int(request.args.get("backup_page", 1))
    start_b = (backup_page - 1) * Config.BACKUP_FILES_PER_PAGE
    end_b = start_b + Config.BACKUP_FILES_PER_PAGE
    backup_slice = backup_dirs[start_b:end_b]
    total_backup_pages = (len(backup_dirs) + Config.BACKUP_FILES_PER_PAGE - 1) // Config.BACKUP_FILES_PER_PAGE

    backup_files = {}
    for d in backup_slice:
        files = [f.name for f in d.iterdir() if f.is_file()]
        backup_files[d.name] = {"files": files, "count": len(files)}

    return render_template(
        "index.html",
        files_to_display=files_to_display,
        page=page,
        total_pages=total_pages,
        start_page=start_page,
        end_page=end_page,
        backup_files=backup_files,
        total_backup_pages=total_backup_pages,
        backup_page=backup_page,
        scripts=all_scripts,           # 기존 리스트 호환
        grouped_scripts=grouped_scripts_sorted, # 카테고리별 분류
        xml_files=xml_files,
    )


@main_bp.route("/process_ips", methods=["POST"])
@login_required
def process_ips():
    ips = request.form.get("ips")
    selected_script = request.form.get("script")
    selected_xml_file = request.form.get("xmlFile")

    if not ips or not selected_script:
        return jsonify({"error": "IP 주소와 스크립트를 모두 입력하세요."}), 400

    xml_file_path = None
    if selected_script == "02-set_config.py" and selected_xml_file:
        xml_path = Path(Config.XML_FOLDER) / selected_xml_file
        if not xml_path.exists():
            return jsonify({"error": "선택한 XML 파일이 존재하지 않습니다."}), 400
        xml_file_path = str(xml_path)

    job_id = str(time.time())
    session["job_id"] = job_id

    ip_files = save_ip_addresses(ips, Config.UPLOAD_FOLDER)
    total_files = len(ip_files)

    handler = FileCreatedHandler(job_id, total_files)
    observer = Observer()
    observer.schedule(handler, Config.IDRAC_INFO_FOLDER, recursive=False)
    observer.start()

    future = executor.submit(
        process_ips_concurrently, ip_files, job_id, observer, selected_script, xml_file_path
    )
    future.add_done_callback(lambda x: on_complete(job_id))

    logging.info(f"[AJAX] 작업 시작: {job_id}, script: {selected_script}")
    return jsonify({"job_id": job_id})


@main_bp.route("/progress_status/<job_id>")
@login_required
def progress_status(job_id: str):
    return jsonify({"progress": get_progress(job_id)})


@main_bp.route("/backup", methods=["POST"])
@login_required
def backup_files():
    prefix = request.form.get("backup_prefix", "")
    if not prefix.startswith("PO"):
        flash("Backup 이름은 PO로 시작해야 합니다.")
        return redirect(url_for("main.index"))

    folder_name = f"{prefix}_{time.strftime('%Y%m%d')}"
    backup_path = Path(Config.BACKUP_FOLDER) / folder_name
    backup_path.mkdir(parents=True, exist_ok=True)

    info_dir = Path(Config.IDRAC_INFO_FOLDER)
    for file in info_dir.iterdir():
        if file.is_file():
            shutil.move(str(file), str(backup_path / file.name))

    flash("백업 완료되었습니다.")
    logging.info(f"백업 완료: {folder_name}")
    return redirect(url_for("main.index"))


@main_bp.route("/download/<filename>")
@login_required
def download_file(filename: str):
    # send_from_directory는 내부적으로 안전 검사를 수행
    return send_from_directory(Config.IDRAC_INFO_FOLDER, filename, as_attachment=True)


@main_bp.route("/delete/<filename>", methods=["POST"])
@login_required
def delete_file(filename: str):
    file_path = Path(Config.IDRAC_INFO_FOLDER) / filename
    if file_path.exists():
        try:
            file_path.unlink()
            flash(f"'{filename}' 파일이 삭제되었습니다.", "success")
            logging.info(f"파일 삭제됨: {filename}")
        except Exception as e:
            logging.error(f"파일 삭제 오류: {e}")
            flash("파일 삭제 중 오류가 발생했습니다.", "danger")
    else:
        flash("파일이 존재하지 않습니다.", "warning")
    return redirect(url_for("main.index"))


@main_bp.route("/download_zip", methods=["POST"])
@login_required
def download_zip():
    zip_filename = request.form.get("zip_filename", "export")
    zip_path = Path(Config.TEMP_ZIP_FOLDER) / f"{zip_filename}.zip"

    with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
        for file in Path(Config.IDRAC_INFO_FOLDER).glob("*"):
            if file.is_file():
                zipf.write(file, arcname=file.name)

    try:
        response = send_file(str(zip_path), as_attachment=True)
        return response
    finally:
        # 응답 후 임시 ZIP 삭제
        try:
            if zip_path.exists():
                zip_path.unlink()
        except Exception as e:
            logging.warning(f"임시 ZIP 삭제 실패: {e}")


@main_bp.route("/download_backup/<date>/<filename>")
@login_required
def download_backup_file(date: str, filename: str):
    backup_path = Path(Config.BACKUP_FOLDER) / date
    return send_from_directory(str(backup_path), filename, as_attachment=True)