import json
import io
import zipfile
from pathlib import Path

from fastapi import APIRouter, HTTPException, UploadFile, File
from fastapi.responses import StreamingResponse
from loguru import logger
from pydantic import BaseModel

from app.config import settings
from app.database import get_db
from app.models import MafileData

router = APIRouter(prefix="/api/mafiles", tags=["mafile_tools"])


@router.get("")
async def list_mafiles():
    """List all stored mafiles."""
    mafiles_dir = settings.mafiles_dir
    if not mafiles_dir.exists():
        return []

    result = []
    for f in sorted(mafiles_dir.glob("*.mafile")):
        try:
            data = json.loads(f.read_text(encoding="utf-8"))
            result.append({
                "filename": f.name,
                "account_name": data.get("account_name", ""),
                "steam_id": data.get("Session", {}).get("SteamID", ""),
                "has_shared_secret": bool(data.get("shared_secret")),
                "has_identity_secret": bool(data.get("identity_secret")),
                "fully_enrolled": data.get("fully_enrolled", False),
            })
        except (json.JSONDecodeError, OSError):
            result.append({"filename": f.name, "error": "Failed to parse"})
    return result


@router.post("/upload")
async def upload_mafiles(files: list[UploadFile] = File(...)):
    """Upload multiple .mafile files."""
    settings.mafiles_dir.mkdir(parents=True, exist_ok=True)
    uploaded = 0
    errors: list[str] = []

    db = await get_db()

    for file in files:
        content = (await file.read()).decode("utf-8", errors="ignore")
        try:
            mafile = MafileData.model_validate_json(content)
            steam_id = mafile.Session.SteamID or mafile.account_name or file.filename
            safe_name = Path(str(steam_id)).name
            dest = settings.mafiles_dir / f"{safe_name}.mafile"
            dest.write_text(content, encoding="utf-8")
            uploaded += 1

            if mafile.account_name:
                mafile_steam_id = str(mafile.Session.SteamID) if mafile.Session.SteamID else None
                await db.execute(
                    """UPDATE accounts
                       SET mafile_path = ?, shared_secret = ?, identity_secret = ?,
                           steam_id = COALESCE(steam_id, ?)
                       WHERE login = ?""",
                    (
                        str(dest),
                        mafile.shared_secret or "",
                        mafile.identity_secret or "",
                        mafile_steam_id,
                        mafile.account_name,
                    ),
                )
                await db.commit()
        except Exception as exc:
            errors.append(f"{file.filename}: {exc}")

    logger.info(f"Uploaded {uploaded} mafiles, {len(errors)} errors")
    return {"uploaded": uploaded, "errors": errors}


@router.get("/{filename}")
async def get_mafile(filename: str):
    """Get parsed mafile data by filename."""
    safe_name = Path(filename).name
    mafile_path = settings.mafiles_dir / safe_name
    if not mafile_path.exists() or not mafile_path.suffix == ".mafile":
        raise HTTPException(status_code=404, detail="Mafile not found")

    content = mafile_path.read_text(encoding="utf-8")
    try:
        return json.loads(content)
    except json.JSONDecodeError:
        raise HTTPException(status_code=500, detail="Failed to parse mafile")


@router.delete("/{filename}", status_code=204)
async def delete_mafile(filename: str):
    safe_name = Path(filename).name
    mafile_path = settings.mafiles_dir / safe_name
    if not mafile_path.exists():
        raise HTTPException(status_code=404, detail="Mafile not found")
    mafile_path.unlink()


@router.get("/export/all")
async def export_all_secrets():
    """Export account_name:shared_secret:identity_secret for all mafiles."""
    mafiles_dir = settings.mafiles_dir
    if not mafiles_dir.exists():
        return []

    result = []
    for f in sorted(mafiles_dir.glob("*.mafile")):
        try:
            data = json.loads(f.read_text(encoding="utf-8"))
            result.append({
                "account_name": data.get("account_name", ""),
                "shared_secret": data.get("shared_secret", ""),
                "identity_secret": data.get("identity_secret", ""),
                "steam_id": data.get("Session", {}).get("SteamID", ""),
            })
        except (json.JSONDecodeError, OSError):
            pass
    return result


class ExportRequest(BaseModel):
    fields: list[str] = []
    session_fields: list[str] = []
    format: str = "flat_mafiles"  # flat_mafiles | per_account_folder | single_file
    account_ids: list[int] = []
    # Naming variables: {username} and {steamid} are replaced at export time
    folder_name_template: str = "{username}"
    mafile_name_template: str = "{steamid}.mafile"
    txt_name_template: str = "{username}.txt"
    include_txt_per_folder: bool = False
    include_global_txt: bool = False
    skip_folders: bool = False
    # Format for .txt lines: login:password:email:email_password
    txt_format: str = "{login}:{password}:{email}:{email_password}"


def _filter_mafile(data: dict, fields: list[str], session_fields: list[str]) -> dict:
    """Keep only selected top-level + Session fields from mafile JSON."""
    if not fields and not session_fields:
        return data

    filtered = {}
    for key in fields:
        if key in data and key != "Session":
            filtered[key] = data[key]

    if session_fields and "Session" in data:
        filtered["Session"] = {
            k: v for k, v in data["Session"].items() if k in session_fields
        }

    return filtered


@router.post("/export/zip")
async def export_mafiles_zip(body: ExportRequest):
    """Build a .zip with filtered mafile data, optionally limited to account IDs."""
    if not body.account_ids:
        raise HTTPException(status_code=400, detail="No account IDs provided for export")

    mafiles_dir = settings.mafiles_dir
    if not mafiles_dir.exists():
        raise HTTPException(status_code=404, detail="No mafiles directory")

    db = await get_db()

    # Load account info for naming and .txt generation
    account_map: dict[str, dict] = {}  # login -> account row
    if body.account_ids:
        placeholders = ",".join("?" * len(body.account_ids))
        cursor = await db.execute(
            f"SELECT * FROM accounts WHERE id IN ({placeholders})",
            body.account_ids,
        )
    else:
        cursor = await db.execute("SELECT * FROM accounts")
    for row in await cursor.fetchall():
        account_map[row["login"]] = dict(row)

    account_logins = set(account_map.keys()) if body.account_ids else set()

    def _resolve_template(template: str, username: str, steam_id: str) -> str:
        return template.replace("{username}", username).replace("{steamid}", steam_id)

    def _resolve_txt_line(template: str, acc: dict, mafile_data: dict | None = None) -> str:
        has_mafile_var = "{mafile}" in template
        if has_mafile_var:
            before, _, after = template.partition("{mafile}")
        else:
            before = template

        line = (
            before
            .replace("{login}", acc.get("login") or "")
            .replace("{password}", acc.get("password") or "")
            .replace("{email}", acc.get("email") or "")
            .replace("{email_password}", acc.get("email_password") or "")
            .replace("{steam_id}", acc.get("steam_id") or "")
            .replace("{proxy}", acc.get("proxy") or "")
        )
        line = line.rstrip(":")
        if has_mafile_var and mafile_data is not None:
            line += ":" + json.dumps(mafile_data, ensure_ascii=False, separators=(",", ":"))
        return line

    buf = io.BytesIO()
    all_data: list[str] = []
    global_txt_lines: list[str] = []
    folder_txt_map: dict[str, list[str]] = {}  # folder_name -> lines

    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
        for f in sorted(mafiles_dir.glob("*.mafile")):
            try:
                raw = json.loads(f.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                continue

            acct_name = raw.get("account_name", "")
            steam_id = str(raw.get("Session", {}).get("SteamID", ""))

            if account_logins:
                if acct_name not in account_logins and steam_id not in {str(a) for a in body.account_ids}:
                    continue

            filtered = _filter_mafile(raw, body.fields, body.session_fields)

            # Find matching account record for txt
            acc = account_map.get(acct_name, {})

            if body.format == "single_file":
                if acc:
                    all_data.append(_resolve_txt_line(body.txt_format, acc, filtered))
                else:
                    all_data.append(json.dumps(filtered, ensure_ascii=False, separators=(",", ":")))
            elif body.format == "per_account_folder":
                folder_name = _resolve_template(body.folder_name_template, acct_name, steam_id)
                mafile_name = _resolve_template(body.mafile_name_template, acct_name, steam_id)

                if body.skip_folders:
                    zf.writestr(mafile_name, json.dumps(filtered, ensure_ascii=False, separators=(",", ":")))
                else:
                    zf.writestr(f"{folder_name}/{mafile_name}", json.dumps(filtered, ensure_ascii=False, separators=(",", ":")))

                    if body.include_txt_per_folder and acc:
                        txt_name = _resolve_template(body.txt_name_template, acct_name, steam_id)
                        line = _resolve_txt_line(body.txt_format, acc, filtered)
                        if folder_name not in folder_txt_map:
                            folder_txt_map[folder_name] = []
                        folder_txt_map[folder_name].append(line)
                        zf.writestr(f"{folder_name}/{txt_name}", line + "\n")
            else:
                mafile_name = _resolve_template(body.mafile_name_template, acct_name, steam_id)
                zf.writestr(mafile_name, json.dumps(filtered, ensure_ascii=False, separators=(",", ":")))

            # Collect global txt line
            if body.include_global_txt and acc:
                global_txt_lines.append(_resolve_txt_line(body.txt_format, acc, filtered))

        if body.format == "single_file" and all_data:
            zf.writestr("accounts.txt", "\n".join(all_data) + "\n")

        if body.include_global_txt and global_txt_lines:
            zf.writestr("accounts.txt", "\n".join(global_txt_lines) + "\n")

    buf.seek(0)
    return StreamingResponse(
        buf,
        media_type="application/zip",
        headers={"Content-Disposition": "attachment; filename=mafiles_export.zip"},
    )
