"""Simple update server — serves update packages from a directory."""
from __future__ import annotations

import hashlib
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Optional

from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import FileResponse, JSONResponse

from mesh.updates.models import UpdateChannel, UpdateInfo

logger = logging.getLogger(__name__)


def create_update_server(
    packages_dir: str = "data/updates",
    admin_token: str = "",
) -> FastAPI:
    """Create a FastAPI app that serves update packages.

    Can run standalone or be mounted on an existing app.
    """
    app = FastAPI(title="Mesh Update Server")
    pkg_dir = Path(packages_dir)
    pkg_dir.mkdir(parents=True, exist_ok=True)
    metadata_path = pkg_dir / "metadata.json"

    def _load_metadata() -> dict:
        if metadata_path.exists():
            return json.loads(metadata_path.read_text())
        return {"packages": {}}

    def _save_metadata(data: dict) -> None:
        metadata_path.write_text(json.dumps(data, indent=2, default=str))

    @app.get("/updates/latest")
    async def latest_update(channel: str = "stable") -> UpdateInfo:
        """Return metadata for the latest version on the given channel."""
        meta = _load_metadata()
        packages = meta.get("packages", {})

        # Find latest version for channel
        candidates = [
            (v, info) for v, info in packages.items()
            if info.get("channel", "stable") == channel
        ]
        if not candidates:
            raise HTTPException(404, f"No updates on channel '{channel}'")

        # Sort by semantic version
        def _ver_key(item):
            try:
                return tuple(int(x) for x in item[0].split("."))
            except (ValueError, AttributeError):
                return (0, 0, 0)

        candidates.sort(key=_ver_key, reverse=True)
        version, info = candidates[0]

        return UpdateInfo(
            version=version,
            channel=UpdateChannel(info.get("channel", "stable")),
            download_url=info.get("download_url", ""),
            sha256=info.get("sha256", ""),
            release_notes=info.get("release_notes", ""),
            size_bytes=info.get("size_bytes", 0),
            min_python_version=info.get("min_python_version", "3.10"),
            published_at=info.get("published_at"),
        )

    @app.get("/updates/download/{version}")
    async def download_update(version: str):
        """Download an update package by version."""
        meta = _load_metadata()
        if version not in meta.get("packages", {}):
            raise HTTPException(404, f"Version {version} not found")

        info = meta["packages"][version]
        filename = info.get("filename", f"mesh-{version}.tar.gz")
        filepath = pkg_dir / filename

        if not filepath.exists():
            raise HTTPException(404, f"Package file not found: {filename}")

        return FileResponse(
            filepath,
            media_type="application/gzip",
            filename=filename,
        )

    @app.post("/updates/publish")
    async def publish_update(
        package: UploadFile = File(...),
        version: str = "",
        channel: str = "stable",
        release_notes: str = "",
        min_python_version: str = "3.10",
    ):
        """Upload and publish a new update package.

        Requires admin_token if configured.
        """
        if not version:
            raise HTTPException(400, "version is required")

        # Save the package file
        filename = f"mesh-{version}.tar.gz"
        filepath = pkg_dir / filename

        content = await package.read()
        filepath.write_bytes(content)

        # Compute SHA-256
        sha256 = hashlib.sha256(content).hexdigest()

        # Determine the server URL for the download link
        # This will be relative — callers can resolve it against the server base
        download_url = f"/updates/download/{version}"

        # Update metadata
        meta = _load_metadata()
        meta["packages"][version] = {
            "channel": channel,
            "filename": filename,
            "sha256": sha256,
            "size_bytes": len(content),
            "release_notes": release_notes,
            "min_python_version": min_python_version,
            "download_url": download_url,
            "published_at": datetime.utcnow().isoformat(),
        }
        _save_metadata(meta)

        logger.info("Published update %s (%.1f MB, channel=%s)", version, len(content) / 1048576, channel)

        return {
            "status": "ok",
            "version": version,
            "sha256": sha256,
            "size_bytes": len(content),
        }

    @app.get("/updates/list")
    async def list_updates(channel: Optional[str] = None):
        """List all available update packages."""
        meta = _load_metadata()
        packages = meta.get("packages", {})

        if channel:
            packages = {
                v: info for v, info in packages.items()
                if info.get("channel", "stable") == channel
            }

        return {"packages": packages}

    return app
