"""Daemon process management: start, stop, status."""

import logging
import os
import signal
import subprocess
import sys
import time
from pathlib import Path

import httpx

from mykb.core.config import get_config_dir, get_config

logger = logging.getLogger(__name__)


def get_pid_file() -> Path:
    """Get the PID file path."""
    return get_config_dir() / 'daemon.pid'


def get_log_file() -> Path:
    """Get the log file path."""
    return get_config_dir() / 'daemon.log'


def get_daemon_config() -> tuple[str, int]:
    """Get daemon host and port from config."""
    config = get_config()
    daemon_config = config.get('daemon', {})
    host = daemon_config.get('host', '127.0.0.1')
    port = daemon_config.get('port', 7230)
    return host, port


def get_daemon_url() -> str:
    """Get the daemon base URL."""
    host, port = get_daemon_config()
    return f'http://{host}:{port}'


def is_daemon_running() -> tuple[bool, int | None]:
    """
    Check if daemon is running.
    Returns: (is_running, pid)
    """
    pid_file = get_pid_file()

    if not pid_file.exists():
        return False, None

    try:
        pid = int(pid_file.read_text().strip())
    except (ValueError, OSError):
        return False, None

    # Check if process exists
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        # Process doesn't exist, clean up stale PID file
        pid_file.unlink(missing_ok=True)
        return False, None
    except PermissionError:
        # Process exists but we don't have permission (still running)
        return True, pid

    # Verify it's actually our daemon by checking health endpoint
    try:
        url = get_daemon_url()
        with httpx.Client(timeout=2.0) as client:
            resp = client.get(f'{url}/health')
            if resp.status_code == 200:
                return True, pid
    except (httpx.ConnectError, httpx.TimeoutException):
        pass

    return False, pid


def wait_for_daemon_ready(timeout: float = 30.0) -> bool:
    """Wait for daemon to become ready."""
    url = get_daemon_url()
    start_time = time.time()

    while time.time() - start_time < timeout:
        try:
            with httpx.Client(timeout=2.0) as client:
                resp = client.get(f'{url}/health')
                if resp.status_code == 200:
                    return True
        except (httpx.ConnectError, httpx.TimeoutException):
            pass
        time.sleep(0.5)

    return False


def start_daemon(foreground: bool = False) -> bool:
    """
    Start the daemon.

    Args:
        foreground: If True, run in foreground (blocking). If False, daemonize.

    Returns:
        True if started successfully.
    """
    running, pid = is_daemon_running()
    if running:
        logger.warning(f'Daemon already running (PID: {pid})')
        return False

    # Ensure config dir exists
    config_dir = get_config_dir()
    config_dir.mkdir(parents=True, exist_ok=True)

    host, port = get_daemon_config()
    pid_file = get_pid_file()
    log_file = get_log_file()

    if foreground:
        # Run in foreground (blocking)
        import uvicorn

        # Write PID before starting
        pid_file.write_text(str(os.getpid()))
        try:
            uvicorn.run(
                'mykb.daemon.app:app',
                host=host,
                port=port,
                log_level='info',
            )
        finally:
            pid_file.unlink(missing_ok=True)
        return True

    # Background mode: spawn subprocess
    cmd = [
        sys.executable,
        '-m',
        'uvicorn',
        'mykb.daemon.app:app',
        '--host',
        host,
        '--port',
        str(port),
        '--log-level',
        'info',
    ]

    # Open log file for output
    with open(log_file, 'a') as log_fh:
        log_fh.write(f'\n--- Starting daemon at {time.strftime("%Y-%m-%d %H:%M:%S")} ---\n')
        log_fh.flush()

        # Start subprocess
        process = subprocess.Popen(
            cmd,
            stdout=log_fh,
            stderr=subprocess.STDOUT,
            stdin=subprocess.DEVNULL,
            start_new_session=True,
        )

    # Write PID file
    pid_file.write_text(str(process.pid))
    logger.info(f'Daemon starting (PID: {process.pid})')

    # Wait for daemon to be ready
    if wait_for_daemon_ready(timeout=60.0):
        logger.info('Daemon started successfully')
        return True
    else:
        logger.error('Daemon failed to start within timeout')
        # Try to clean up
        try:
            process.terminate()
            process.wait(timeout=5)
        except Exception:
            pass
        pid_file.unlink(missing_ok=True)
        return False


def stop_daemon() -> bool:
    """
    Stop the daemon.

    Returns:
        True if stopped successfully.
    """
    running, pid = is_daemon_running()

    if not running:
        logger.info('Daemon is not running')
        # Clean up stale PID file if exists
        get_pid_file().unlink(missing_ok=True)
        return True

    if pid is None:
        logger.error('Could not determine daemon PID')
        return False

    logger.info(f'Stopping daemon (PID: {pid})')

    # Send SIGTERM
    try:
        os.kill(pid, signal.SIGTERM)
    except ProcessLookupError:
        logger.info('Process already terminated')
        get_pid_file().unlink(missing_ok=True)
        return True

    # Wait for process to exit
    for _ in range(30):  # 15 seconds max
        time.sleep(0.5)
        try:
            os.kill(pid, 0)
        except ProcessLookupError:
            logger.info('Daemon stopped')
            get_pid_file().unlink(missing_ok=True)
            return True

    # Force kill if still running
    logger.warning('Daemon did not stop gracefully, sending SIGKILL')
    try:
        os.kill(pid, signal.SIGKILL)
        time.sleep(1)
    except ProcessLookupError:
        pass

    get_pid_file().unlink(missing_ok=True)
    return True


def get_daemon_status() -> dict:
    """
    Get daemon status.

    Returns:
        Status dict with keys: running, pid, url, health (if running)
    """
    running, pid = is_daemon_running()
    url = get_daemon_url()

    status = {
        'running': running,
        'pid': pid,
        'url': url,
        'pid_file': str(get_pid_file()),
        'log_file': str(get_log_file()),
    }

    if running:
        # Get health info
        try:
            with httpx.Client(timeout=5.0) as client:
                resp = client.get(f'{url}/health')
                if resp.status_code == 200:
                    status['health'] = resp.json()
        except (httpx.ConnectError, httpx.TimeoutException):
            status['health'] = None

    return status
