import time
import json
import uuid
import os
from fastapi import APIRouter, HTTPException, Depends, Header, Query
from pydantic import BaseModel
from typing import Optional, Any, Dict, List
from jsonschema import validate, ValidationError
from app.models.user import get_db_connection
from app.api.v1.auth import get_current_user, decode_token

router = APIRouter()

SCHEMA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "models", "project_schema.json")

def upgrade_project_json_if_needed(project_data: dict) -> dict:
    if "main_session" in project_data:
        return project_data
    
    tracks = project_data.get("tracks", [])
    # Legacy format stores item start times in SECONDS; convert using the real
    # seconds-per-bar (old code hardcoded /4.0 which shifted every item's
    # position for any tempo other than the one where 1 bar = 4s).
    bpm_val = float(project_data.get("bpm", 120.0) or 120.0)
    seconds_per_bar = (60.0 / bpm_val) * 4
    upgraded_tracks = []
    for t in tracks:
        track_id = str(t.get("id", ""))
        track_name = t.get("name", "Track")
        vol = t.get("volumeDb", 0.0)
        pan = t.get("pan", 0.0)
        muted = t.get("muted", False)
        solo = t.get("solo", False)
        
        items = []
        for c in t.get("clips", []):
            items.append({
                "id": c.get("id"),
                "name": c.get("name", "Audio Clip"),
                "type": "AUDIO_ITEM",
                "start_bar": round(c.get("startTime", 0.0) / seconds_per_bar, 6),
                "duration_bars": round((c.get("duration", 4.0) if c.get("duration") else 4.0) / seconds_per_bar, 6),
                "clip_start_offset_bars": 0.0,
                "source_data": {
                    "audio_file_url": f"/static/audio/uploads/{t.get('serverFileId')}" if t.get("serverFileId") else "",
                    "server_file_id": t.get("serverFileId") or None,
                    "sample_rate": 44100,
                    "channels": 2,
                    "gain": 1.0
                }
            })
        for m in t.get("midiItems", []):
            items.append({
                "id": m.get("id"),
                "name": m.get("name", "MIDI Item"),
                "type": "MIDI_ITEM",
                "start_bar": round(m.get("startTime", 0.0) / seconds_per_bar, 6),
                "duration_bars": round((m.get("duration", 4.0) or 4.0) / seconds_per_bar, 6),
                "clip_start_offset_bars": 0.0,
                "source_data": {
                    "total_buffer_bars": round((m.get("duration", 8.0) or 8.0) / seconds_per_bar, 6),
                    "notes": m.get("notes", [])
                }
            })
        
        upgraded_tracks.append({
            "id": track_id,
            "name": track_name,
            "type": "MIDI" if t.get("midiItems") else "AUDIO",
            "volume_db": vol,
            "pan": pan,
            "mute": muted,
            "solo": solo,
            "fx_chain": [],
            "synth_engine": {
                "plugin_id": "synth",
                "preset_id": "default",
                "parameters": {}
            },
            "items": items
        })
        
    return {
        "project_id": project_data.get("id", "temp_project"),
        "metadata": {
            "title": project_data.get("name", "Dự án mới"),
            "bpm": 120.0,
            "time_signature_numerator": 4,
            "time_signature_denominator": 4,
            "sample_rate": 44100
        },
        "main_session": {
            "id": "main",
            "name": "MAIN SESSION",
            "is_root": True,
            "length_bars": 16.0,
            "auto_compute_length": True,
            "tracks": upgraded_tracks
        },
        "section_store": {}
    }

def validate_project_data(data_json: str) -> str:
    try:
        data = json.loads(data_json)
        if "project_id" not in data:
            data["project_id"] = "temp_legacy_" + str(int(time.time()))
        # Strip null synth_engine from tracks (breaks schema validation)
        for session_key in ["main_session"] + [k for k in data.get("section_store", {})]:
            session = data.get(session_key)
            if not session:
                continue
            for track in session.get("tracks", []):
                if "synth_engine" in track and track["synth_engine"] is None:
                    del track["synth_engine"]
        if "main_session" not in data:
            data = upgrade_project_json_if_needed(data)
            data_json = json.dumps(data)
            
        if os.path.exists(SCHEMA_PATH):
            with open(SCHEMA_PATH, "r") as f:
                schema = json.load(f)
            validate(instance=data, schema=schema)
        return data_json
    except json.JSONDecodeError as e:
        raise HTTPException(status_code=400, detail=f"Cấu trúc JSON không hợp lệ: {str(e)}")
    except ValidationError as e:
        path = " -> ".join(str(p) for p in e.path)
        raise HTTPException(status_code=400, detail=f"Lỗi xác thực project schema tại [{path}]: {e.message}")
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Lỗi xác thực dự án: {str(e)}")

class SaveProjectRequest(BaseModel):
    name: str
    data_json: str

class SaveTempProjectRequest(BaseModel):
    data_json: str
    client_id: Optional[str] = None  # client ghi bản này (LAN browser / Tauri UI) — chống ping-pong sync

def get_optional_user(authorization: Optional[str] = Header(None)) -> Optional[dict]:
    if authorization and authorization.startswith("Bearer "):
        token = authorization.split(" ")[1]
        return decode_token(token)
    return None

@router.post("/temp")
async def save_temp_project(req: SaveTempProjectRequest, current_user: Optional[dict] = Depends(get_optional_user)):
    validated_data_json = validate_project_data(req.data_json)
    user_id = current_user["user_id"] if current_user else "anonymous"
    conn = get_db_connection()
    cursor = conn.cursor()
    
    size_bytes = len(validated_data_json.encode("utf-8"))
    now = time.time()
    temp_id = f"temp_{user_id}"
    
    cursor.execute("""
    INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at)
    VALUES (?, ?, 'Dự án tạm chưa lưu', ?, 1, ?, ?)
    ON CONFLICT(id) DO UPDATE SET data_json = excluded.data_json, size_bytes = excluded.size_bytes, updated_at = excluded.updated_at
    """, (temp_id, user_id, validated_data_json, size_bytes, now))
    
    conn.commit()
    conn.close()
    # ── Ghi file autosave vào thư mục temp CỦA ỨNG DỤNG (storage/temp) ──
    # Yêu cầu: "Khi tắt ứng dụng → tự động lưu temp trên thư mục temp của ứng
    # dụng để khi load lại thì tải lại dự án đang làm dở." Ngoài row trong DB,
    # ghi thẳng file JSON để luôn có bản sao thật trên ổ đĩa OS.
    try:
        from app.config import settings as _st
        temp_dir = os.path.join(_st.STORAGE_DIR, "temp")
        os.makedirs(temp_dir, exist_ok=True)
        with open(os.path.join(temp_dir, "autosave.json"), "w", encoding="utf-8") as f:
            json.dump({"user_id": user_id, "updated_at": now, "data_json": validated_data_json}, f, ensure_ascii=False)
        # Revision sidecar (nhỏ) — realtime sync LAN/Tauri đọc updated_at +
        # client_id KHÔNG cần parse data_json (có thể MB).
        with open(os.path.join(temp_dir, f"revision_{user_id}.json"), "w", encoding="utf-8") as f:
            json.dump({"client_id": req.client_id or "", "updated_at": now}, f, ensure_ascii=False)
    except Exception:
        pass
    return {"message": "Đã lưu dự án tạm tự động", "updated_at": now}

@router.get("/temp")
async def get_temp_project(current_user: Optional[dict] = Depends(get_optional_user)):
    user_id = current_user["user_id"] if current_user else "anonymous"
    temp_id = f"temp_{user_id}"
    
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT data_json, updated_at FROM projects WHERE id = ? AND is_temp = 1", (temp_id,))
    row = cursor.fetchone()
    conn.close()
    
    if row:
        return {
            "has_temp": True,
            "data_json": row["data_json"],
            "updated_at": row["updated_at"]
        }
    # Fallback: file autosave.json trong thư mục temp của ứng dụng (khi lưu lúc
    # đóng app qua sendBeacon — user anonymous) — tải lại dự án đang làm dở.
    try:
        from app.config import settings as _st
        autosave_path = os.path.join(_st.STORAGE_DIR, "temp", "autosave.json")
        if os.path.isfile(autosave_path):
            with open(autosave_path, "r", encoding="utf-8") as f:
                data = json.load(f)
            if data.get("data_json"):
                return {
                    "has_temp": True,
                    "data_json": data["data_json"],
                    "updated_at": data.get("updated_at", 0),
                    "source": "file",
                }
    except Exception:
        pass
    return {"has_temp": False}


@router.get("/temp/revision")
async def temp_project_revision(current_user: Optional[dict] = Depends(get_optional_user)):
    """Revision nhẹ của bản temp: updated_at + client_id — frontend poll 2-3s
    để realtime sync giữa LAN browser và Tauri standalone (không tải full
    data_json mỗi lần). client_id = client ghi bản cuối → client khác bỏ qua
    bản do CHÍNH NÓ ghi (chống ping-pong)."""
    user_id = current_user["user_id"] if current_user else "anonymous"
    client_id = ""
    updated_at = 0.0
    try:
        from app.config import settings as _st
        rev_path = os.path.join(_st.STORAGE_DIR, "temp", f"revision_{user_id}.json")
        if os.path.isfile(rev_path):
            with open(rev_path, "r", encoding="utf-8") as f:
                meta = json.load(f)
            client_id = meta.get("client_id", "") or ""
            updated_at = float(meta.get("updated_at", 0) or 0)
    except Exception:
        pass
    if not updated_at:
        conn = get_db_connection()
        cursor = conn.cursor()
        cursor.execute("SELECT updated_at FROM projects WHERE id = ? AND is_temp = 1", (f"temp_{user_id}",))
        row = cursor.fetchone()
        conn.close()
        if row:
            updated_at = float(row["updated_at"] or 0)
    return {"updated_at": updated_at, "client_id": client_id}


def _os_projects_dir() -> str:
    """Thư mục dự án trên hệ điều hành (Ctrl-S desktop):
    Windows → Documents/SonicForgeDAW/Projects (fallback USERPROFILE);
    Linux/macOS → ~/SonicForgeDAW/Projects. Luôn tồn tại (tự tạo)."""
    try:
        if os.name == "nt":
            docs = os.path.join(os.environ.get("USERPROFILE") or os.path.expanduser("~"), "Documents")
            base = docs if os.path.isdir(docs) else (os.environ.get("USERPROFILE") or os.path.expanduser("~"))
        else:
            base = os.path.expanduser("~")
        d = os.path.join(base, "SonicForgeDAW", "Projects")
        os.makedirs(d, exist_ok=True)
        return d
    except Exception:
        return os.path.join(os.path.expanduser("~"), "SonicForgeDAW", "Projects")


@router.post("/save-to-disk")
async def save_project_to_disk(req: SaveProjectRequest, authorization: Optional[str] = Header(None)):
    """Ctrl-S trên desktop: lưu project ra THƯ MỤC CỦA HỆ ĐIỀU HÀNH
    (Documents/SonicForgeDAW/Projects — bản desktop). Docker/headless KHÔNG
    dùng endpoint này (frontend lưu Cloud). Auth optional — desktop có thể
    chưa login."""
    try:
        if authorization and authorization.startswith("Bearer "):
            decode_token(authorization.split(" ")[1])
    except Exception:
        pass
    validated_data_json = validate_project_data(req.data_json)
    safe_name = "".join(c for c in (req.name or "Dự án mới") if c.isalnum() or c in " _-.").strip() or "Du-an-moi"
    if len(safe_name) > 80:
        safe_name = safe_name[:80].strip()
    safe_name = safe_name.replace(".", "_") if safe_name.endswith(".") else safe_name
    fname = safe_name + ".sonicforge.json"
    out_dir = _os_projects_dir()
    out_path = os.path.join(out_dir, fname)
    # Không ghi đè file đang mở ở nơi khác? Ghi đè OK (Ctrl-S = save).
    with open(out_path, "w", encoding="utf-8") as f:
        f.write(validated_data_json)
    return {
        "success": True,
        "name": req.name or "Dự án mới",
        "path": out_path,
        "filename": fname,
    }


@router.post("/cloud")
async def save_cloud_project(req: SaveProjectRequest, current_user: dict = Depends(get_current_user)):
    validated_data_json = validate_project_data(req.data_json)
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    
    cursor.execute("SELECT storage_limit_mb FROM user_quotas WHERE user_id = ?", (user_id,))
    quota_row = cursor.fetchone()
    storage_limit_mb = quota_row["storage_limit_mb"] if quota_row else 500
    
    cursor.execute("SELECT SUM(size_bytes) as total_used FROM projects WHERE user_id = ? AND is_temp = 0", (user_id,))
    used_row = cursor.fetchone()
    used_bytes = used_row["total_used"] if used_row and used_row["total_used"] else 0
    
    new_size_bytes = len(validated_data_json.encode("utf-8"))
    max_bytes = storage_limit_mb * 1024 * 1024
    
    if used_bytes + new_size_bytes > max_bytes:
        conn.close()
        raise HTTPException(
            status_code=400,
            detail=f"Dung lượng dự án vượt quá hạn mức Quota ({storage_limit_mb}MB). Vui lòng dọn dẹp hoặc nâng cấp tài khoản."
        )
        
    project_id = str(uuid.uuid4())
    now = time.time()
    
    cursor.execute("""
    INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at)
    VALUES (?, ?, ?, ?, 0, ?, ?)
    """, (project_id, user_id, req.name, validated_data_json, new_size_bytes, now))
    
    temp_id = f"temp_{user_id}"
    cursor.execute("DELETE FROM projects WHERE id = ? AND is_temp = 1", (temp_id,))
    
    conn.commit()
    conn.close()
    
    return {
        "message": "Đã lưu dự án lên Cloud thành công!",
        "project_id": project_id
    }

@router.get("/cloud")
async def list_cloud_projects(current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("""
    SELECT p.id, p.name, p.size_bytes, p.updated_at,
           (SELECT COUNT(*) FROM project_backups pb WHERE pb.project_id = p.id AND pb.user_id = p.user_id) as backup_count
    FROM projects p
    WHERE p.user_id = ? AND p.is_temp = 0
    ORDER BY p.updated_at DESC
    """, (user_id,))
    rows = cursor.fetchall()
    conn.close()
    
    return [
        {
            "id": r["id"],
            "name": r["name"],
            "size_mb": round(r["size_bytes"] / (1024 * 1024), 2),
            "updated_at": r["updated_at"],
            "backup_count": r["backup_count"]
        } for r in rows
    ]

@router.get("/cloud/{project_id}")
async def get_cloud_project(project_id: str, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT name, data_json FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
    row = cursor.fetchone()
    conn.close()
    
    if not row:
        raise HTTPException(status_code=404, detail="Không tìm thấy dự án")
        
    return {
        "id": project_id,
        "name": row["name"],
        "data_json": row["data_json"]
    }

@router.delete("/cloud/{project_id}")
async def delete_cloud_project(project_id: str, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("DELETE FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
    conn.commit()
    conn.close()
    return {"success": True, "message": "Đã xóa dự án thành công"}

@router.put("/cloud/{project_id}")
async def update_cloud_project(project_id: str, req: SaveProjectRequest, current_user: dict = Depends(get_current_user)):
    validated_data_json = validate_project_data(req.data_json)
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    
    cursor.execute("SELECT id, size_bytes FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
    existing = cursor.fetchone()
    if not existing:
        conn.close()
        raise HTTPException(status_code=404, detail="Không tìm thấy dự án để cập nhật")
    
    new_size_bytes = len(validated_data_json.encode("utf-8"))
    
    # Enforce storage quota (same rule as save_cloud_project — previously
    # update bypassed the quota entirely).
    cursor.execute("SELECT storage_limit_mb FROM user_quotas WHERE user_id = ?", (user_id,))
    quota_row = cursor.fetchone()
    storage_limit_mb = quota_row["storage_limit_mb"] if quota_row else 500
    cursor.execute("SELECT SUM(size_bytes) as total_used FROM projects WHERE user_id = ? AND is_temp = 0", (user_id,))
    used_row = cursor.fetchone()
    used_bytes = (used_row["total_used"] if used_row and used_row["total_used"] else 0) - (existing["size_bytes"] or 0)
    max_bytes = storage_limit_mb * 1024 * 1024
    if used_bytes + new_size_bytes > max_bytes:
        conn.close()
        raise HTTPException(
            status_code=400,
            detail=f"Dung lượng dự án vượt quá hạn mức Quota ({storage_limit_mb}MB). Vui lòng dọn dẹp hoặc nâng cấp tài khoản."
        )
    
    now = time.time()
    
    cursor.execute("""
    UPDATE projects
    SET name = ?, data_json = ?, size_bytes = ?, updated_at = ?
    WHERE id = ? AND user_id = ?
    """, (req.name, validated_data_json, new_size_bytes, now, project_id, user_id))
    
    conn.commit()
    conn.close()
    return {"success": True, "message": "Đã cập nhật dự án thành công"}

class BackupConfigRequest(BaseModel):
    max_count: int = 10

@router.post("/cloud/{project_id}/backup")
async def create_project_backup(project_id: str, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT name, data_json FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
    row = cursor.fetchone()
    if not row:
        conn.close()
        raise HTTPException(status_code=404, detail="Không tìm thấy dự án")
    
    backup_id = f"backup_{uuid.uuid4().hex[:12]}"
    now = time.time()
    size_bytes = len(row["data_json"].encode("utf-8"))
    backup_name = f"[Backup] {row['name']} ({time.strftime('%Y-%m-%d %H:%M', time.localtime(now))})"
    
    cursor.execute("""
    INSERT INTO project_backups (id, user_id, project_id, name, data_json, size_bytes, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?)
    """, (backup_id, user_id, project_id, backup_name, row["data_json"], size_bytes, now))
    
    # Enforce retention limit: lấy max_count từ query param mặc định 10
    cursor.execute("SELECT COUNT(*) as cnt FROM project_backups WHERE project_id = ? AND user_id = ?", (project_id, user_id))
    count = cursor.fetchone()["cnt"]
    max_backup = 10  # default
    if count > max_backup:
        excess = count - max_backup
        cursor.execute("""
        DELETE FROM project_backups WHERE id IN (
            SELECT id FROM project_backups WHERE project_id = ? AND user_id = ?
            ORDER BY created_at ASC LIMIT ?
        )
        """, (project_id, user_id, excess))
    
    conn.commit()
    conn.close()
    return {
        "backup_id": backup_id,
        "name": backup_name,
        "created_at": now,
        "max_backups": max_backup
    }

@router.get("/cloud/{project_id}/backups")
async def list_project_backups(project_id: str, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("""
    SELECT id, name, size_bytes, created_at FROM project_backups
    WHERE project_id = ? AND user_id = ?
    ORDER BY created_at DESC
    """, (project_id, user_id))
    rows = cursor.fetchall()
    conn.close()
    return [
        {
            "id": r["id"],
            "name": r["name"],
            "size_mb": round(r["size_bytes"] / (1024 * 1024), 2),
            "created_at": r["created_at"]
        } for r in rows
    ]

@router.delete("/cloud/backups/{backup_id}")
async def delete_project_backup(backup_id: str, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("DELETE FROM project_backups WHERE id = ? AND user_id = ?", (backup_id, user_id))
    conn.commit()
    conn.close()
    return {"success": True, "message": "Đã xóa bản backup"}

class CleanupBackupsRequest(BaseModel):
    keep: int = 10

@router.post("/cloud/backups/cleanup")
async def cleanup_all_backups(req: CleanupBackupsRequest, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    keep = max(5, min(20, req.keep))
    conn = get_db_connection()
    cursor = conn.cursor()
    
    # Lấy tất cả project_id của user
    cursor.execute("SELECT DISTINCT project_id FROM project_backups WHERE user_id = ?", (user_id,))
    projects = cursor.fetchall()
    total_deleted = 0
    for p in projects:
        pid = p["project_id"]
        cursor.execute("SELECT COUNT(*) as cnt FROM project_backups WHERE project_id = ? AND user_id = ?", (pid, user_id))
        cnt = cursor.fetchone()["cnt"]
        if cnt > keep:
            excess = cnt - keep
            cursor.execute("""
            DELETE FROM project_backups WHERE id IN (
                SELECT id FROM project_backups WHERE project_id = ? AND user_id = ?
                ORDER BY created_at ASC LIMIT ?
            )
            """, (pid, user_id, excess))
            total_deleted += excess
    
    conn.commit()
    conn.close()
    return {"success": True, "deleted": total_deleted, "keep": keep}

class RenderProjectRequest(BaseModel):
    sample_rate: Optional[int] = 44100

@router.post("/cloud/{project_id}/render")
async def render_project_endpoint(project_id: str, req: RenderProjectRequest, current_user: dict = Depends(get_current_user)):
    user_id = current_user["user_id"]
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT name, data_json FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
    row = cursor.fetchone()
    conn.close()
    
    if not row:
        raise HTTPException(status_code=404, detail="Không tìm thấy dự án để kết xuất")
    
    # Validate schema
    validate_project_data(row["data_json"])
    
    # Trigger Celery task
    from app.tasks.worker import render_project_task
    task = render_project_task.delay(
        project_id=project_id,
        project_name=row["name"],
        project_json_str=row["data_json"],
        sample_rate=req.sample_rate or 44100
    )
    
    return {
        "task_id": task.id,
        "status": "processing"
    }
