# 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

"""
Service specifications for self-hosted server provisioning.

Defines the catalog of services that Familiar can provision and manage,
including container images, port mappings, volume mounts, and health checks.
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List


class ServiceType(str, Enum):
    """How the service is run."""

    NATIVE = "native"  # Pure Python, in-process
    CONTAINER = "container"  # Podman / Docker container


@dataclass
class ServiceSpec:
    """Specification for a provisionable service."""

    key: str
    display_name: str
    description: str
    icon: str  # FontAwesome class (fa-*)
    service_type: ServiceType

    # Container fields
    image: str = ""
    ports: Dict[int, int] = field(default_factory=dict)  # host → container
    volumes: Dict[str, str] = field(default_factory=dict)  # host → container
    env_vars: Dict[str, str] = field(default_factory=dict)

    # Health check
    health_check_path: str = ""
    health_check_port: int = 0

    # Native service
    native_module: str = ""  # e.g. "familiar.skills.email.server"

    # Metadata
    default_container_name: str = ""
    doc_url: str = ""
    job_classes: List[str] = field(default_factory=list)
    connect_key: str = ""  # Key in connect wizard, if any
    min_ram_gb: float = 0.5

    def __post_init__(self):
        if not self.default_container_name and self.service_type == ServiceType.CONTAINER:
            self.default_container_name = f"familiar-{self.key}"

    @property
    def volume_base(self) -> str:
        """Base path for this service's persistent data."""
        return f"~/.familiar/services/{self.key}"

    def get_resolved_volumes(self, home: str) -> Dict[str, str]:
        """Resolve ~ in volume paths to actual home directory."""
        resolved = {}
        for host_path, container_path in self.volumes.items():
            resolved_host = host_path.replace("~", home)
            resolved[resolved_host] = container_path
        return resolved

    def to_dict(self) -> dict:
        """Serialize for API responses."""
        return {
            "key": self.key,
            "display_name": self.display_name,
            "description": self.description,
            "icon": self.icon,
            "service_type": self.service_type.value,
            "image": self.image,
            "ports": self.ports,
            "health_check_path": self.health_check_path,
            "health_check_port": self.health_check_port,
            "default_container_name": self.default_container_name,
            "doc_url": self.doc_url,
            "job_classes": self.job_classes,
            "connect_key": self.connect_key,
            "min_ram_gb": self.min_ram_gb,
        }


# =============================================================================
# SERVICE CATALOG
# =============================================================================

SERVICE_SPECS: Dict[str, ServiceSpec] = {}


def _register(spec: ServiceSpec) -> ServiceSpec:
    SERVICE_SPECS[spec.key] = spec
    return spec


# ── Native services ──────────────────────────────────────────────────

_register(
    ServiceSpec(
        key="email_server",
        display_name="Email Server",
        description="Built-in SMTP/IMAP email server powered by Familiar's pure-Python stack.",
        icon="fa-envelope",
        service_type=ServiceType.NATIVE,
        native_module="familiar.skills.email.server",
        health_check_path="/internal/status",
        health_check_port=0,  # In-process check
        doc_url="https://github.com/omegcrash/familiar/wiki/Email-Server",
        job_classes=["helper", "business_buddy", "social_worker", "nonprofit_director",
                     "chef", "artist"],
        connect_key="email",
        min_ram_gb=0.1,
    )
)

# ── Container services ──────────────────────────────────────────────

_register(
    ServiceSpec(
        key="gitea",
        display_name="Gitea",
        description="Lightweight self-hosted Git service (Forgejo fork). "
        "Host your own repositories, issues, and CI/CD.",
        icon="fa-code-branch",
        service_type=ServiceType.CONTAINER,
        image="codeberg.org/forgejo/forgejo:10",
        ports={3000: 3000, 2222: 22},
        volumes={
            "~/.familiar/services/gitea/data": "/data",
        },
        env_vars={
            "USER_UID": "1000",
            "USER_GID": "1000",
        },
        health_check_path="/api/v1/settings/api",
        health_check_port=3000,
        doc_url="https://forgejo.org/docs/latest/",
        job_classes=["artist", "business_buddy", "nonprofit_director"],
        connect_key="gitea",
        min_ram_gb=0.5,
    )
)

_register(
    ServiceSpec(
        key="mealie",
        display_name="Mealie",
        description="Recipe manager and meal planner. "
        "Import recipes from any URL, plan meals, generate shopping lists.",
        icon="fa-utensils",
        service_type=ServiceType.CONTAINER,
        image="ghcr.io/mealie-recipes/mealie:latest",
        ports={9925: 9000},
        volumes={
            "~/.familiar/services/mealie/data": "/app/data",
        },
        env_vars={
            "ALLOW_SIGNUP": "true",
            "TZ": "UTC",
            "BASE_URL": "http://localhost:9925",
        },
        health_check_path="/api/app/about",
        health_check_port=9925,
        doc_url="https://docs.mealie.io/",
        job_classes=["chef"],
        connect_key="mealie",
        min_ram_gb=0.5,
    )
)

_register(
    ServiceSpec(
        key="joplin",
        display_name="Joplin Server",
        description="Open-source note-taking and to-do application server. "
        "Sync notes across devices with end-to-end encryption.",
        icon="fa-sticky-note",
        service_type=ServiceType.CONTAINER,
        image="joplin/server:latest",
        ports={22300: 22300},
        volumes={
            "~/.familiar/services/joplin/data": "/data",
        },
        env_vars={
            "APP_PORT": "22300",
            "APP_BASE_URL": "http://localhost:22300",
            "DB_CLIENT": "sqlite3",
        },
        health_check_path="/api/ping",
        health_check_port=22300,
        doc_url="https://joplinapp.org/help/",
        job_classes=["social_worker", "business_buddy", "nonprofit_director",
                     "helper", "chef", "artist"],
        connect_key="joplin",
        min_ram_gb=0.5,
    )
)

_register(
    ServiceSpec(
        key="pihole",
        display_name="Pi-hole",
        description="Network-wide ad blocker and DNS sinkhole. "
        "Blocks ads and trackers for all devices on your network.",
        icon="fa-shield-alt",
        service_type=ServiceType.CONTAINER,
        image="pihole/pihole:latest",
        ports={8053: 80, 53: 53},
        volumes={
            "~/.familiar/services/pihole/etc": "/etc/pihole",
            "~/.familiar/services/pihole/dnsmasq": "/etc/dnsmasq.d",
        },
        env_vars={
            "TZ": "UTC",
            "WEBPASSWORD": "familiar",
        },
        health_check_path="/admin/api.php?summary",
        health_check_port=8053,
        doc_url="https://docs.pi-hole.net/",
        job_classes=[],
        min_ram_gb=0.25,
    )
)

_register(
    ServiceSpec(
        key="nextcloud",
        display_name="Nextcloud",
        description="Self-hosted productivity platform — files, calendar, contacts, "
        "and collaborative editing.",
        icon="fa-cloud",
        service_type=ServiceType.CONTAINER,
        image="nextcloud:stable",
        ports={8080: 80},
        volumes={
            "~/.familiar/services/nextcloud/data": "/var/www/html",
        },
        env_vars={
            "NEXTCLOUD_ADMIN_USER": "admin",
            "NEXTCLOUD_ADMIN_PASSWORD": "familiar",
        },
        health_check_path="/status.php",
        health_check_port=8080,
        doc_url="https://docs.nextcloud.com/",
        job_classes=["artist", "social_worker", "business_buddy", "nonprofit_director"],
        connect_key="nextcloud",
        min_ram_gb=1.0,
    )
)

_register(
    ServiceSpec(
        key="jellyfin",
        display_name="Jellyfin",
        description="Free media system for streaming your movies, TV shows, "
        "music, and photos.",
        icon="fa-film",
        service_type=ServiceType.CONTAINER,
        image="jellyfin/jellyfin:latest",
        ports={8096: 8096},
        volumes={
            "~/.familiar/services/jellyfin/config": "/config",
            "~/.familiar/services/jellyfin/cache": "/cache",
        },
        env_vars={},
        health_check_path="/System/Info/Public",
        health_check_port=8096,
        doc_url="https://jellyfin.org/docs/",
        job_classes=["artist"],
        min_ram_gb=1.0,
    )
)

_register(
    ServiceSpec(
        key="searxng",
        display_name="SearXNG",
        description="Privacy-respecting metasearch engine. "
        "Aggregate results from 70+ search engines without tracking.",
        icon="fa-search",
        service_type=ServiceType.CONTAINER,
        image="searxng/searxng:latest",
        ports={8888: 8080},
        volumes={
            "~/.familiar/services/searxng/data": "/etc/searxng",
        },
        env_vars={
            "SEARXNG_BASE_URL": "http://localhost:8888/",
        },
        health_check_path="/healthz",
        health_check_port=8888,
        doc_url="https://docs.searxng.org/",
        job_classes=["helper"],
        connect_key="searxng",
        min_ram_gb=0.25,
    )
)

# ── Social / Federation services ──────────────────────────────────

_register(
    ServiceSpec(
        key="gotosocial",
        display_name="GoToSocial",
        description="Lightweight ActivityPub social media server. "
        "Mastodon-compatible federation.",
        icon="fa-hashtag",
        service_type=ServiceType.CONTAINER,
        image="superseriousbusiness/gotosocial:latest",
        ports={8081: 8080},
        volumes={
            "~/.familiar/services/gotosocial/data": "/gotosocial/storage",
        },
        env_vars={
            "GTS_HOST": "localhost:8081",
            "GTS_DB_TYPE": "sqlite",
            "GTS_DB_ADDRESS": "/gotosocial/storage/sqlite.db",
            "GTS_LETSENCRYPT_ENABLED": "false",
            "GTS_ACCOUNTS_REGISTRATION_OPEN": "false",
        },
        health_check_path="/api/v1/instance",
        health_check_port=8081,
        doc_url="https://docs.gotosocial.org/",
        job_classes=["artist", "social_worker", "nonprofit_director", "business_buddy"],
        connect_key="mastodon",
        min_ram_gb=0.25,
    )
)

_register(
    ServiceSpec(
        key="bluesky_pds",
        display_name="Bluesky PDS",
        description="Personal Data Server for the AT Protocol. "
        "Self-host your Bluesky identity.",
        icon="fa-cloud-upload-alt",
        service_type=ServiceType.CONTAINER,
        image="ghcr.io/bluesky-social/pds:latest",
        ports={2583: 3000},
        volumes={
            "~/.familiar/services/bluesky_pds/data": "/pds",
        },
        env_vars={
            "PDS_HOSTNAME": "localhost",
            "PDS_DATA_DIRECTORY": "/pds",
            "PDS_BLOBSTORE_DISK_LOCATION": "/pds/blocks",
            "PDS_DID_PLC_URL": "https://plc.directory",
            "PDS_BSKY_APP_VIEW_URL": "https://api.bsky.app",
            "PDS_BSKY_APP_VIEW_DID": "did:web:api.bsky.app",
            "PDS_REPORT_SERVICE_URL": "https://mod.bsky.app",
            "PDS_REPORT_SERVICE_DID": "did:plc:ar7c4by46qjdydhdevvrndac",
            "PDS_CRAWLERS": "https://bsky.network",
            "LOG_ENABLED": "true",
        },
        health_check_path="/xrpc/_health",
        health_check_port=2583,
        doc_url="https://github.com/bluesky-social/pds",
        job_classes=["artist", "social_worker", "nonprofit_director"],
        connect_key="bluesky",
        min_ram_gb=0.5,
    )
)
