"""VvvebJS 網頁管理器 — Flask 主程式."""

from __future__ import annotations

import json
import re
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import os
import uuid

from flask import Flask, Response, abort, jsonify, render_template, request, send_from_directory
from werkzeug.utils import secure_filename

from utils.media_manager import save_upload, list_media, delete_media

app = Flask(__name__)

# ── 路徑常數 ────────────────────────────────────────────────────────────────
BASE_DIR = Path(__file__).parent
WEBSITES_DIR = BASE_DIR / "websites"
BLANK_TEMPLATE = BASE_DIR / "static" / "Vvvebjs" / "new-page-blank-template.html"


# ── 輔助函式 ────────────────────────────────────────────────────────────────

def slugify(text: str) -> str:
    """將文字轉成 URL-safe slug."""
    text = text.lower().strip()
    text = re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE)
    text = re.sub(r"[\s_-]+", "-", text)
    text = re.sub(r"^-+|-+$", "", text)
    return text or "untitled"


def _now_iso() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")


def _project_dir(slug: str) -> Path:
    return WEBSITES_DIR / slug


def _project_json_path(slug: str) -> Path:
    return _project_dir(slug) / "project.json"


def _load_project(slug: str) -> dict[str, Any]:
    """讀取專案設定，若不存在則回傳預設值."""
    path = _project_json_path(slug)
    if path.exists():
        data: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
        return data
    return {
        "name": slug,
        "slug": slug,
        "description": "",
        "created_at": _now_iso(),
    }


def _save_project(slug: str, data: dict[str, Any]) -> None:
    path = _project_json_path(slug)
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def _list_pages(slug: str) -> list[str]:
    """列出專案目錄下所有 .html 頁面（包含子資料夾）並回傳相對路徑."""
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        return []
    # 排除專案設定檔、寮本檔等非頁面檔
    return sorted(
        str(p.relative_to(proj_dir)).replace("\\", "/")
        for p in proj_dir.rglob("*.html")
        if p.name != "project.json"
    )


def _project_summary(slug: str) -> dict[str, Any]:
    data = _load_project(slug)
    pages = _list_pages(slug)
    proj_dir = _project_dir(slug)
    html_files = list(proj_dir.rglob("*.html"))
    if html_files:
        last_mod = max(f.stat().st_mtime for f in html_files)
        last_mod_str = datetime.fromtimestamp(last_mod, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
    else:
        last_mod_str = str(data.get("created_at", _now_iso()))
    return {
        **data,
        "slug": slug,
        "page_count": len(pages),
        "pages": pages,
        "last_modified": last_mod_str,
    }


def _sanitize_file_path(slug: str, filename: str, folder: str = "") -> Path | None:
    """驗證並回傳安全的檔案路徑，支援子資料夾（防止路徑遍歷）."""
    proj_dir = _project_dir(slug).resolve()
    safe_filename = Path(filename).name  # 只取最後的檔名，防止路徑注入
    if folder:
        safe_folder = str(Path(folder)).lstrip("/\\").replace("..", "")
        target = (proj_dir / safe_folder / safe_filename).resolve()
    else:
        target = (proj_dir / safe_filename).resolve()
    if not str(target).startswith(str(proj_dir)):
        return None
    if target.suffix.lower() != ".html":
        return None
    return target


def _sanitize_rel_path(slug: str, rel_path: str) -> Path | None:
    """驗證並回傳安全的相對路徑（支援子資料夾）."""
    proj_dir = _project_dir(slug).resolve()
    clean = rel_path.lstrip("/\\").replace("..", "")
    target = (proj_dir / clean).resolve()
    if not str(target).startswith(str(proj_dir)):
        return None
    if target.suffix.lower() != ".html":
        return None
    return target


def _copy_blank_template(dest: Path) -> None:
    """複製空白範本或建立最小 HTML."""
    if BLANK_TEMPLATE.exists():
        shutil.copy(BLANK_TEMPLATE, dest)
    else:
        dest.write_text(
            "<!DOCTYPE html><html><head><title>My Page</title></head><body></body></html>",
            encoding="utf-8",
        )


def build_page_tree(slug: str, max_depth: int = 3) -> dict[str, Any]:
    """遞歸建構專案的頁面樹狀結構（根據資料夾結構）。"""
    proj_dir = _project_dir(slug)

    def scan_folder(folder: Path, current_depth: int = 0):
        items: list[dict[str, Any]] = []
        if current_depth > max_depth:
            return items
        try:
            names = sorted([p.name for p in folder.iterdir()])
        except Exception:
            return items

        for name in names:
            if name.startswith("."):
                continue
            full = folder / name
            if full.is_dir():
                children = scan_folder(full, current_depth + 1)
                items.append({
                    "type": "folder",
                    "name": name,
                    "title": name.replace("-", " ").title(),
                    "children": children,
                })
            elif full.is_file() and full.suffix.lower() == ".html":
                rel = str(full.relative_to(proj_dir)).replace("\\", "/")
                items.append({
                    "type": "file",
                    "name": rel,
                    "title": Path(name).stem.replace("-", " ").title(),
                })
        return items

    return {"root": scan_folder(proj_dir, 0)}


# ── 路由：一般頁面 ──────────────────────────────────────────────────────────

@app.route("/")  # type: ignore[untyped-decorator]
def index() -> Response:
    from flask import redirect, url_for
    return redirect(url_for("dashboard"))


@app.route("/dashboard")  # type: ignore[untyped-decorator]
def dashboard() -> str:
    # render projects overview
    return str(render_template("dashboard.html"))


@app.route("/dashboard/project/<slug>")  # type: ignore[untyped-decorator]
def project_dashboard(slug: str) -> str:
    # render single project management dashboard
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        abort(404)
    return str(render_template("project_dashboard.html", slug=slug))


@app.route("/editor/<slug>")  # type: ignore[untyped-decorator]
def editor(slug: str) -> str:
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        abort(404)
    pages = _list_pages(slug)  # 現在回傳相對路徑列表，如 'index.html', 'sub/about.html'
    project = _load_project(slug)

    pages_obj: dict[str, Any] = {}
    for rel_path in pages:
        name = rel_path.replace(".html", "")
        stem = Path(rel_path).stem.replace("-", " ").title()
        folder_part = name.rsplit("/", 1)[0] if "/" in name else ""
        pages_obj[name] = {
            "name": name,
            "title": stem,
            "folder": folder_part,
            "filename": rel_path,
            "file": rel_path,
            "url": f"/sites/{slug}/{rel_path}",
        }

    if not pages_obj:
        idx_path = proj_dir / "index.html"
        _copy_blank_template(idx_path)
        pages_obj["index"] = {
            "name": "index",
            "title": "Index",
            "filename": "index.html",
            "file": "index.html",
            "url": f"/sites/{slug}/index.html",
        }

    return str(render_template(
        "editor.html",
        slug=slug,
        project=project,
        pages_json=json.dumps(pages_obj, ensure_ascii=False),
    ))


# ── 路由：API ───────────────────────────────────────────────────────────────

@app.route("/api/projects", methods=["GET"])  # type: ignore[untyped-decorator]
def api_list_projects() -> Response:
    WEBSITES_DIR.mkdir(exist_ok=True)
    projects = [_project_summary(d.name) for d in sorted(WEBSITES_DIR.iterdir()) if d.is_dir()]
    return jsonify(projects)


@app.route("/api/projects/<slug>/settings", methods=["GET"])  # type: ignore[untyped-decorator]
def api_get_settings(slug: str) -> tuple[Response, int] | Response:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    data = _load_project(slug)
    settings = data.get("settings", {})
    # provide sensible defaults
    defaults = {
        "title": data.get("name", slug),
        "description": data.get("description", ""),
        "logo_url": None,
        "favicon_url": None,
        "primary_color": "#007bff",
        "secondary_color": "#6c757d",
    }
    merged = {**defaults, **settings}
    return jsonify(merged)


@app.route("/api/projects/<slug>/settings", methods=["PUT"])  # type: ignore[untyped-decorator]
def api_put_settings(slug: str) -> tuple[Response, int] | Response:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    body: dict[str, Any] = request.get_json(force=True) or {}
    data = _load_project(slug)
    settings = data.get("settings", {})
    settings.update(body)
    data["settings"] = settings
    data["updated_at"] = _now_iso()
    _save_project(slug, data)
    return jsonify({"ok": True, "settings": settings})



@app.route("/api/projects", methods=["POST"])  # type: ignore[untyped-decorator]
def api_create_project() -> tuple[Response, int]:
    body: dict[str, Any] = request.get_json(force=True) or {}
    name: str = str(body.get("name", "")).strip()
    if not name:
        return jsonify({"error": "名稱不能為空"}), 400

    slug = slugify(name)
    proj_dir = _project_dir(slug)
    base_slug = slug
    counter = 1
    while proj_dir.exists():
        slug = f"{base_slug}-{counter}"
        proj_dir = _project_dir(slug)
        counter += 1

    proj_dir.mkdir(parents=True, exist_ok=True)
    data: dict[str, Any] = {
        "name": name,
        "slug": slug,
        "description": str(body.get("description", "")),
        "created_at": _now_iso(),
    }
    _save_project(slug, data)
    _copy_blank_template(proj_dir / "index.html")
    return jsonify(_project_summary(slug)), 201


@app.route("/api/projects/<slug>", methods=["GET"])  # type: ignore[untyped-decorator]
def api_get_project(slug: str) -> tuple[Response, int] | Response:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    return jsonify(_project_summary(slug))


@app.route("/api/projects/<slug>", methods=["PUT"])  # type: ignore[untyped-decorator]
def api_update_project(slug: str) -> tuple[Response, int] | Response:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    body: dict[str, Any] = request.get_json(force=True) or {}
    data = _load_project(slug)
    if "name" in body and str(body["name"]).strip():
        data["name"] = str(body["name"]).strip()
    if "description" in body:
        data["description"] = str(body["description"])
    data["updated_at"] = _now_iso()
    _save_project(slug, data)
    return jsonify(_project_summary(slug))


@app.route("/api/projects/<slug>", methods=["DELETE"])  # type: ignore[untyped-decorator]
def api_delete_project(slug: str) -> tuple[Response, int] | Response:
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        return jsonify({"error": "專案不存在"}), 404
    shutil.rmtree(proj_dir)
    return jsonify({"ok": True})


@app.route("/api/projects/<slug>/pages", methods=["GET"])  # type: ignore[untyped-decorator]
def api_list_pages(slug: str) -> tuple[Response, int] | Response:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    return jsonify(_list_pages(slug))


@app.route("/api/projects/<slug>/pages-tree", methods=["GET"])  # type: ignore[untyped-decorator]
def api_pages_tree(slug: str) -> tuple[Response, int] | Response:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    tree = build_page_tree(slug, max_depth=5)
    return jsonify(tree)


@app.route("/api/projects/<slug>/pages-tree", methods=["PUT"])  # type: ignore[untyped-decorator]
def api_put_pages_tree(slug: str) -> tuple[Response, int] | Response:
    """儲存自訂的頁面樹狀結構到 project.json 的 page_tree 欄位。"""
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    body: dict[str, Any] = request.get_json(force=True) or {}
    if not isinstance(body, dict):
        return jsonify({"error": "不正確的資料格式"}), 400
    data = _load_project(slug)
    data["page_tree"] = body
    data["updated_at"] = _now_iso()
    _save_project(slug, data)
    return jsonify({"ok": True, "page_tree": body})


@app.route("/api/projects/<slug>/media/upload", methods=["POST"])  # type: ignore[untyped-decorator]
def api_media_upload(slug: str) -> tuple[Response, int] | Response:
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        return jsonify({"error": "專案不存在"}), 404
    if "file" not in request.files:
        return jsonify({"error": "缺少檔案 (file)"}), 400
    f = request.files["file"]
    if f.filename == "":
        return jsonify({"error": "檔名為空"}), 400
    # basic validation
    filename = secure_filename(f.filename)
    meta = save_upload(proj_dir, f)
    # substitute slug in returned urls
    if isinstance(meta.get("url"), str):
        meta["url"] = meta["url"].replace("{slug}", slug)
    if isinstance(meta.get("thumb"), str):
        meta["thumb"] = meta["thumb"].replace("{slug}", slug)
    return jsonify({"ok": True, "file": meta})


@app.route("/api/projects/<slug>/media/list", methods=["GET"])  # type: ignore[untyped-decorator]
def api_media_list(slug: str) -> tuple[Response, int] | Response:
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        return jsonify({"error": "專案不存在"}), 404
    items = list_media(proj_dir)
    # patch urls
    for it in items:
        if "filename" in it and "date" in it:
            it["url"] = f"/sites/{slug}/media/images/{it['date']}/{it['filename']}"
    return jsonify(items)


@app.route("/api/projects/<slug>/media/<path:rel_path>", methods=["DELETE"])  # type: ignore[untyped-decorator]
def api_media_delete(slug: str, rel_path: str) -> tuple[Response, int] | Response:
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        return jsonify({"error": "專案不存在"}), 404
    ok = delete_media(proj_dir, rel_path)
    if not ok:
        return jsonify({"error": "刪除失敗或檔案不存在"}), 400
    return jsonify({"ok": True})


@app.route("/api/projects/<slug>/pages", methods=["POST"])  # type: ignore[untyped-decorator]
def api_create_page(slug: str) -> tuple[Response, int]:
    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404
    body: dict[str, Any] = request.get_json(force=True) or {}
    page_name: str = str(body.get("name", "")).strip()
    if not page_name:
        return jsonify({"error": "頁面名稱不能為空"}), 400
    page_slug = slugify(page_name)
    page_path = _project_dir(slug) / f"{page_slug}.html"
    if page_path.exists():
        return jsonify({"error": "頁面已存在"}), 409
    _copy_blank_template(page_path)
    return jsonify({"page": f"{page_slug}.html"}), 201


@app.route("/api/save", methods=["POST"])  # type: ignore[untyped-decorator]
def api_save() -> tuple[Response, int] | Response:
    """接受 vvvebjs 的儲存或新增頁面請求，寫入對應專案目錄."""
    if request.is_json:
        raw: dict[str, Any] = request.get_json(force=True) or {}
        body: dict[str, Any] = raw
    else:
        form = dict(request.form)
        body = {k: (v[0] if isinstance(v, list) else v) for k, v in form.items()}

    slug: str = str(body.get("slug", "")).strip()
    if not slug:
        # Fallback to query parameter if not in body (e.g. for some raw requests)
        slug = request.args.get("slug", "").strip()

    if not slug:
        return jsonify({"error": "缺少專案識別碼 (slug)"}), 400

    if not _project_dir(slug).exists():
        return jsonify({"error": "專案不存在"}), 404

    action = request.args.get("action", "").strip()

    # ── 處理重新命名 / 複製頁面 ──
    if action == "rename":
        file = str(body.get("file", "")).strip()
        newfile = str(body.get("newfile", "")).strip()
        new_title = str(body.get("title", "")).strip()
        new_folder = str(body.get("folder", "")).strip()
        duplicate_str = str(body.get("duplicate", "")).strip().lower()
        is_duplicate = (duplicate_str == "true")

        if not file or not newfile:
            return jsonify({"error": "缺少參數 file 或 newfile"}), 400

        old_path = _sanitize_rel_path(slug, file)
        new_path = _sanitize_file_path(slug, newfile, new_folder)

        if old_path is None or new_path is None:
            return jsonify({"error": "不合法的頁面名稱"}), 400

        if not old_path.exists():
            return jsonify({"error": "來源頁面不存在"}), 404

        if new_path.exists() and old_path != new_path:
            return jsonify({"error": "目標頁面已存在"}), 409

        # 建立子資料夾（若需要）
        new_path.parent.mkdir(parents=True, exist_ok=True)

        if is_duplicate:
            shutil.copy(old_path, new_path)
            msg = "頁面複製成功"
        else:
            old_path.rename(new_path)
            msg = "頁面重新命名成功"

        # 計算相對於專案目錄的路徑，用於 URL
        proj_dir = _project_dir(slug)
        rel_path = new_path.relative_to(proj_dir)
        page_name = str(rel_path).replace("\\", "/").replace(".html", "")

        return jsonify({
            "success": True,
            "ok": True,
            "message": msg,
            "newfile": str(rel_path).replace("\\", "/"),
            "title": new_title or new_path.stem.replace("-", " ").title(),
            "name": page_name,
            "url": f"/sites/{slug}/{str(rel_path).replace(chr(92), '/')}"
        })

    # ── 處理刪除頁面 ──
    elif action == "delete":
        file = str(body.get("file", "")).strip()
        if not file:
            return jsonify({"error": "缺少參數 file"}), 400

        if file.lower() == "index.html":
            return jsonify({"error": "無法刪除主頁 index.html"}), 400

        safe_path = _sanitize_file_path(slug, file)
        if safe_path is None:
            return jsonify({"error": "不合法的頁面名稱"}), 400

        if not safe_path.exists():
            return jsonify({"error": "頁面不存在"}), 404

        safe_path.unlink()
        return jsonify({
            "success": True,
            "ok": True,
            "message": "頁面已成功刪除"
        })

    # 1. 判斷是否為新增頁面請求 (含有 startTemplateUrl)
    start_template_url = str(body.get("startTemplateUrl", "")).strip()
    if start_template_url:
        title = str(body.get("title", "")).strip() or "New Page"
        folder = str(body.get("folder", "")).strip()
        # 取得安全的檔名
        filename = Path(str(body.get("file", "untitled.html")).strip()).name
        if not filename.endswith(".html"):
            filename += ".html"

        safe_path = _sanitize_file_path(slug, filename, folder)
        if safe_path is None:
            return jsonify({"error": "不合法的頁面名稱"}), 400

        if safe_path.exists():
            return jsonify({"error": "頁面已存在"}), 409

        # 建立子資料夾（若需要）
        safe_path.parent.mkdir(parents=True, exist_ok=True)

        # 解析並複製樣板
        template_source = BASE_DIR / "static" / "Vvvebjs" / start_template_url
        if template_source.exists() and template_source.is_file():
            shutil.copy(template_source, safe_path)
        else:
            _copy_blank_template(safe_path)

        # 回傳 VvvebJS FileManager 所期待的 JSON 格式
        proj_dir = _project_dir(slug)
        rel_path = safe_path.relative_to(proj_dir)
        page_name = str(rel_path).replace("\\", "/").replace(".html", "")

        return jsonify({
            "ok": True,
            "name": page_name,
            "title": title,
            "file": str(rel_path).replace("\\", "/"),
            "url": f"/sites/{slug}/{str(rel_path).replace(chr(92), '/')}"
        })

    # 2. 一般儲存頁面請求（支援子資料夾路徑）
    filename = str(body.get("file", "")).strip()
    html: str = str(body.get("html", "")).strip()

    if not filename or not html:
        return jsonify({"error": "缺少必要參數 file / html"}), 400

    safe_path = _sanitize_rel_path(slug, filename)
    if safe_path is None:
        return jsonify({"error": "不合法的檔案路徑"}), 400

    safe_path.parent.mkdir(parents=True, exist_ok=True)
    safe_path.write_text(html, encoding="utf-8")
    return jsonify({"ok": True, "saved": str(safe_path.relative_to(_project_dir(slug))).replace("\\", "/")})



@app.route("/sites/<slug>/<path:filename>")  # type: ignore[untyped-decorator]
def serve_site_file(slug: str, filename: str) -> Response:
    proj_dir = _project_dir(slug)
    if not proj_dir.exists():
        abort(404)
    return send_from_directory(proj_dir, filename)


# ── 啟動 ────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    WEBSITES_DIR.mkdir(exist_ok=True)
    app.run(debug=True, port=5000)
