"""Full discovery: the complete read-only inventory.

Orchestrates the individual discovery modules, writes the snapshot to
/var/lib/wolfpanel/discovery/last.json, and optionally uploads it to the
Central API. Strictly read-only — no server state is changed.
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from config import Config
from discovery import git, projects, services, system, sftp, databases
from logger import get_logger

log = get_logger("discovery.full")

import re

# Common locations for vhost configs and TLS certificates (listing only).
_VHOST_DIRS = ("/etc/nginx/sites-enabled", "/etc/nginx/conf.d", "/etc/apache2/sites-enabled")
_CERT_DIRS = ("/etc/letsencrypt/live", "/etc/ssl/certs")


def _parse_nginx_config(content: str) -> tuple[str | None, str | None]:
    """Parse server_name and root from a config file using simple regex/string checks."""
    server_name = None
    root_path = None
    for line in content.splitlines():
        if "#" in line:
            line = line.split("#", 1)[0]
        line = line.strip()
        if not line:
            continue
            
        if not server_name:
            m = re.match(r"^server_name\s+([^;]+);", line)
            if m:
                server_name = m.group(1).strip()
                
        if not root_path:
            m = re.match(r"^root\s+([^;]+);", line)
            if m:
                val = m.group(1).strip()
                if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
                    val = val[1:-1]
                root_path = val
                
    return server_name, root_path


def _vhost_files() -> list[dict[str, Any]]:
    """List vhost config file paths and parse server_name/root_path."""
    found: list[dict[str, Any]] = []
    for directory in _VHOST_DIRS:
        base = Path(directory)
        if base.is_dir():
            for p in base.glob("*"):
                if p.is_file():
                    path_str = str(p)
                    server_name = None
                    root_path = None
                    try:
                        content = p.read_text(encoding="utf-8", errors="ignore")
                        server_name, root_path = _parse_nginx_config(content)
                    except Exception:
                        pass
                    found.append({
                        "category": "vhosts",
                        "path": path_str,
                        "server_name": server_name,
                        "root_path": root_path,
                    })
    return found


def _ssl_certificates() -> list[dict[str, Any]]:
    """Locate certificate files and read their details via openssl if present.

    SECURITY: only certificates (public) are inspected — never private keys.
    """
    certs: list[dict[str, Any]] = []
    for directory in _CERT_DIRS:
        base = Path(directory)
        if not base.is_dir():
            continue
        for cert in list(base.glob("**/*.pem"))[:200] + list(base.glob("**/fullchain*")):
            if "privkey" in cert.name or "key" in cert.name.lower():
                continue  # never touch keys
            enddate = system.run_readonly(
                ["openssl", "x509", "-enddate", "-noout", "-in", str(cert)]
            )
            cert_details = system.run_readonly(
                ["openssl", "x509", "-noout", "-text", "-in", str(cert)]
            )
            
            cn = None
            dns_names = []
            if cert_details:
                subject_m = re.search(r"Subject:\s*(.*?)(?:\n|$)", cert_details)
                if subject_m:
                    subject_line = subject_m.group(1)
                    cn_m = re.search(r"\bCN\s*=\s*([^,\/\n]+)", subject_line)
                    if cn_m:
                        cn = cn_m.group(1).strip()
                dns_names = re.findall(r"DNS:([a-zA-Z0-9.*-]+)", cert_details)
                
            all_domains = []
            if cn:
                all_domains.append(cn)
            for name in dns_names:
                if name not in all_domains:
                    all_domains.append(name)
            domains_str = ",".join(all_domains) if all_domains else None
            
            provider = "letsencrypt" if "/etc/letsencrypt/" in str(cert).replace("\\", "/") else "custom"
            cert_name = None
            if provider == "letsencrypt":
                m = re.search(r"/etc/letsencrypt/live/([^/]+)", str(cert).replace("\\", "/"))
                if m:
                    cert_name = m.group(1)

            certs.append(
                {
                    "category": "ssl_certificates",
                    "path": str(cert),
                    "not_after": enddate.strip().split("=", 1)[-1] if enddate else None,
                    "domains": domains_str,
                    "provider": provider,
                    "cert_name": cert_name,
                }
            )
    return certs


def collect() -> dict[str, Any]:
    """Build the full inventory dictionary."""
    web = services.web_servers()
    return {
        "os": system.os_info(),
        "kernel": system.kernel(),
        "cpu_count": system.cpu_count(),
        "memory": system.memory_usage(),
        "disk": system.disk_usage(),
        "load_average": system.load_average(),
        "uptime": system.uptime_seconds(),
        "private_ips": system.private_ips(),
        "public_ip": system.public_ip(),
        "network_interfaces": system.network_interfaces(),
        "open_ports": system.open_ports(),
        "systemd_services": services.systemd_services(),
        "web_servers": web,
        "language_runtimes": services.language_runtimes(),
        "containers": services.containers(),
        "databases": services.databases(),
        "cron_jobs": services.cron_jobs(),
        "vhosts": _vhost_files(),
        "ssl_certificates": _ssl_certificates(),
        "git_repositories": git.find_repositories(),
        "projects": projects.find_projects(),
        "sftp_users": sftp.discover_sftp_users(),
        "database_schemas": databases.discover_database_schemas(),
        "database_users": databases.discover_database_users(),
    }


def run(config: Config, api_client, upload: bool = True) -> dict[str, Any]:
    """Collect, persist the snapshot, and optionally upload it.

    Returns the snapshot dict. Upload failures are logged but never fatal.
    """
    inventory = collect()
    snapshot = {
        "server_id": config.server_id,
        "agent_version": config.agent_version,
        "collected_at": datetime.now(timezone.utc).isoformat(),
        "discovery": inventory,
    }

    # Persist last snapshot for drift detection / offline inspection.
    config.discovery_dir.mkdir(parents=True, exist_ok=True)
    snapshot_path = config.discovery_dir / "last.json"
    snapshot_path.write_text(json.dumps(snapshot, indent=2), encoding="utf-8")
    log.info("discovery snapshot written to %s", snapshot_path)

    if upload:
        from discovery.upload import categories_from_inventory, upload_inventory

        try:
            upload_inventory(
                config,
                api_client,
                "full",
                categories_from_inventory(inventory),
            )
        except Exception as exc:  # noqa: BLE001 - upload is best-effort
            log.warning("discovery upload failed (will rely on local snapshot): %s", exc)

    return snapshot
