from __future__ import annotations
import argparse
import json
import shutil
import threading
import uuid
import webbrowser
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Any

from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import uvicorn

from .config import PACKAGE_ROOT, load_config, resolve_registered_path
from .data import load_and_validate
from .runner import run_analysis

app = FastAPI(title="GRAV-BRIDGE-I", version="0.1.0")
STATIC = PACKAGE_ROOT / "web" / "static"
RUNTIME = PACKAGE_ROOT / "outputs" / "web_runs"
RUNTIME.mkdir(parents=True, exist_ok=True)
app.mount("/static", StaticFiles(directory=STATIC), name="static")

_jobs: dict[str, dict[str, Any]] = {}
_lock = threading.Lock()
_executor = ThreadPoolExecutor(max_workers=1)


def _update(job_id: str, **values):
    with _lock:
        _jobs[job_id].update(values)


def _job_runner(job_id: str, radial: Path, signatures: Path, config_path: Path | None, overrides: dict[str, Any]):
    output = RUNTIME / job_id
    try:
        _update(job_id, state="RUNNING", progress=0.0, message="Starting")
        result = run_analysis(
            radial, signatures, config_path, output, overrides,
            lambda p, m: _update(job_id, progress=p, message=m),
        )
        _update(job_id, state="COMPLETE", progress=1.0, message="Complete", result=result,
                package=str(output / "GRAV_BRIDGE_I_RUN_PACKAGE.zip"))
    except Exception as exc:
        _update(job_id, state="FAILED", message=str(exc), error=repr(exc))


@app.get("/", response_class=HTMLResponse)
def index():
    return (STATIC / "index.html").read_text(encoding="utf-8")


@app.get("/api/status")
def status():
    config = load_config()
    return {
        "chamber": config["chamber"], "version": config["version"],
        "registered_radial": str(resolve_registered_path(config, "radial")),
        "registered_signatures": str(resolve_registered_path(config, "signatures")),
        "ready": True,
    }


@app.post("/api/validate")
def validate_default():
    config = load_config()
    prepared = load_and_validate(resolve_registered_path(config, "radial"), resolve_registered_path(config, "signatures"), config)
    return prepared.report


@app.post("/api/jobs")
async def create_job(
    signature_permutations: int = Form(50),
    target_permutations: int = Form(50),
    bootstrap: int = Form(2000),
    radial_file: UploadFile | None = File(None),
    signature_file: UploadFile | None = File(None),
    config_file: UploadFile | None = File(None),
):
    if not (0 <= signature_permutations <= 2000 and 0 <= target_permutations <= 2000):
        raise HTTPException(400, "Permutation counts must be between 0 and 2000.")
    if not (100 <= bootstrap <= 100000):
        raise HTTPException(400, "Bootstrap repetitions must be between 100 and 100000.")

    config = load_config()
    job_id = uuid.uuid4().hex[:12]
    upload_dir = RUNTIME / job_id / "inputs"
    upload_dir.mkdir(parents=True, exist_ok=True)

    async def save_upload(upload: UploadFile | None, default: Path, filename: str):
        if upload is None:
            return default
        path = upload_dir / filename
        with path.open("wb") as handle:
            shutil.copyfileobj(upload.file, handle)
        return path

    radial = await save_upload(radial_file, resolve_registered_path(config, "radial"), "radial.csv")
    signatures = await save_upload(signature_file, resolve_registered_path(config, "signatures"), "signatures.csv")
    config_path = await save_upload(config_file, None, "config.json") if config_file else None
    overrides = {
        "nulls": {"signature_permutations": signature_permutations, "target_curve_permutations": target_permutations},
        "uncertainty": {"galaxy_bootstrap_repetitions": bootstrap},
    }
    with _lock:
        _jobs[job_id] = {"id": job_id, "state": "QUEUED", "progress": 0.0, "message": "Queued"}
    _executor.submit(_job_runner, job_id, radial, signatures, config_path, overrides)
    return _jobs[job_id]


@app.get("/api/jobs/{job_id}")
def job_status(job_id: str):
    with _lock:
        if job_id not in _jobs:
            raise HTTPException(404, "Unknown job")
        return _jobs[job_id]


@app.get("/api/jobs/{job_id}/download")
def download_job(job_id: str):
    with _lock:
        job = _jobs.get(job_id)
    if not job or job.get("state") != "COMPLETE":
        raise HTTPException(404, "Completed package not available")
    return FileResponse(job["package"], filename=f"GRAV_BRIDGE_I_{job_id}.zip")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8765)
    parser.add_argument("--open-browser", action="store_true")
    args = parser.parse_args()
    if args.open_browser:
        threading.Timer(1.2, lambda: webbrowser.open(f"http://{args.host}:{args.port}")).start()
    uvicorn.run("grav_bridge_i.web:app", host=args.host, port=args.port, reload=False)


if __name__ == "__main__":
    main()
