# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Marketplace — Job Class Package Validator, Installer, and Manager.

Package format (JSON):
{
    "schema_version": "1.0",
    "type": "job_class",
    "metadata": {
        "id": "nonprofit_director",
        "name": "Nonprofit Director",
        "version": "1.0.0",
        "author": "...",
        "description": "...",
        "requires_skills": ["nonprofit", "bookkeeping"],
        "familiar_version": ">=1.14.52"
    },
    "job_class": { ... }  # JobClass fields
}

Packages are stored in ~/.familiar/data/custom_job_classes/{id}.json
"""

import json
import logging
import re
from pathlib import Path
from typing import Optional

from ..core.paths import DATA_DIR

logger = logging.getLogger(__name__)

SCHEMA_VERSION = "1.0"
CUSTOM_DIR = DATA_DIR / "custom_job_classes"

# Required fields in a job class package
_REQUIRED_METADATA = {"id", "name", "version"}
_REQUIRED_JOB_CLASS = {"key", "name", "description", "prompt_fragment"}
_VALID_TYPES = {"job_class", "skill", "bundle", "species", "familiar"}


def _version_tuple(v: str) -> tuple:
    """Parse version string to comparable tuple."""
    return tuple(int(x) for x in re.findall(r"\d+", v))


def validate_package(data: dict) -> list[str]:
    """Validate a marketplace package. Returns list of error strings (empty = valid)."""
    errors = []

    # Schema version
    sv = data.get("schema_version", "")
    if not sv:
        errors.append("Missing 'schema_version'")
    elif sv != SCHEMA_VERSION:
        errors.append(f"Unsupported schema_version '{sv}' (expected '{SCHEMA_VERSION}')")

    # Type
    pkg_type = data.get("type", "")
    if not pkg_type:
        errors.append("Missing 'type'")
    elif pkg_type not in _VALID_TYPES:
        errors.append(f"Invalid type '{pkg_type}' (expected one of: {', '.join(_VALID_TYPES)})")

    # Metadata
    meta = data.get("metadata", {})
    if not meta:
        errors.append("Missing 'metadata' section")
    else:
        for field in _REQUIRED_METADATA:
            if not meta.get(field):
                errors.append(f"Missing metadata.{field}")

        # Version format
        version = meta.get("version", "")
        if version and not re.match(r"^\d+\.\d+\.\d+$", version):
            errors.append(f"Invalid version format '{version}' (expected X.Y.Z)")

        # Familiar version constraint
        fv = meta.get("familiar_version", "")
        if fv:
            match = re.match(r">=(\d+\.\d+\.\d+)", fv)
            if not match:
                errors.append(f"Invalid familiar_version constraint '{fv}' (expected '>=X.Y.Z')")

    # Job class content
    if pkg_type == "job_class":
        jc = data.get("job_class", {})
        if not jc:
            errors.append("Missing 'job_class' section")
        else:
            for field in _REQUIRED_JOB_CLASS:
                if not jc.get(field):
                    errors.append(f"Missing job_class.{field}")

    return errors


def check_compatibility(data: dict) -> Optional[str]:
    """Check if this package is compatible with the current Familiar version.

    Returns error message if incompatible, None if compatible.
    """
    from familiar import __version__

    meta = data.get("metadata", {})
    constraint = meta.get("familiar_version", "")
    if not constraint:
        return None

    match = re.match(r">=(\d+\.\d+\.\d+)", constraint)
    if not match:
        return None

    required = _version_tuple(match.group(1))
    current = _version_tuple(__version__)

    if current < required:
        return f"Package requires familiar>={match.group(1)} but current version is {__version__}"
    return None


def install_package(data: dict) -> str:
    """Install a marketplace package. Returns status message."""
    errors = validate_package(data)
    if errors:
        return "Package validation failed:\n" + "\n".join(f"  - {e}" for e in errors)

    compat = check_compatibility(data)
    if compat:
        return f"Compatibility error: {compat}"

    meta = data.get("metadata", {})
    pkg_id = meta["id"]
    pkg_type = data.get("type", "job_class")

    if pkg_type == "job_class":
        return _install_job_class(data, pkg_id)
    elif pkg_type == "bundle":
        return _install_bundle(data, pkg_id)
    else:
        return f"Installation of type '{pkg_type}' not supported via this module."


def _install_job_class(data: dict, pkg_id: str) -> str:
    """Install a job class package."""
    CUSTOM_DIR.mkdir(parents=True, exist_ok=True)

    target = CUSTOM_DIR / f"{pkg_id}.json"
    existing = target.exists()

    target.write_text(json.dumps(data, indent=2))

    # Register immediately
    from . import JobClass, register_job_class

    jc_data = data["job_class"]
    jc = JobClass(**{k: v for k, v in jc_data.items() if k in JobClass.__dataclass_fields__})
    register_job_class(jc)

    action = "Updated" if existing else "Installed"
    meta = data.get("metadata", {})
    return (
        f"{action} job class '{meta.get('name', pkg_id)}' v{meta.get('version', '?')}\n"
        f"Key: {jc.key}"
    )


def _install_bundle(data: dict, pkg_id: str) -> str:
    """Install a bundle containing a job class and skills."""
    results = []

    if "job_class" in data:
        result = _install_job_class(data, pkg_id)
        results.append(result)

    skills = data.get("skills", [])
    if skills:
        from ..skills._marketplace import install_skill_package

        for skill_data in skills:
            result = install_skill_package(skill_data)
            results.append(result)

    return "\n".join(results) if results else "Bundle is empty."


def uninstall_package(pkg_id: str) -> str:
    """Uninstall a custom job class package."""
    target = CUSTOM_DIR / f"{pkg_id}.json"
    if not target.exists():
        return f"Package '{pkg_id}' not found."

    target.unlink()

    from . import JOB_CLASS_REGISTRY

    if pkg_id in JOB_CLASS_REGISTRY:
        del JOB_CLASS_REGISTRY[pkg_id]

    return f"Uninstalled package '{pkg_id}'."


def list_packages() -> list[dict]:
    """List installed custom job class packages."""
    if not CUSTOM_DIR.exists():
        return []

    packages = []
    for f in sorted(CUSTOM_DIR.glob("*.json")):
        try:
            data = json.loads(f.read_text())
            meta = data.get("metadata", {})
            packages.append(
                {
                    "id": meta.get("id", f.stem),
                    "name": meta.get("name", f.stem),
                    "version": meta.get("version", "unknown"),
                    "type": data.get("type", "job_class"),
                    "file": str(f),
                }
            )
        except Exception:
            packages.append({"id": f.stem, "name": f.stem, "version": "error", "file": str(f)})

    return packages


def load_package_file(path: str) -> dict:
    """Load a package from a JSON file path."""
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(f"Package file not found: {path}")
    return json.loads(p.read_text())
