"""Daemon process management: double-fork, PID files, signal handling.

IMPORTANT: Audio modules (sounddevice, ScreenCaptureKit, deepgram, faster_whisper)
must NOT be imported at module level here. They are imported inside run_daemon()
which is called only after daemonize(). This prevents CoreAudio IPC crashes
that occur when Python forks after loading audio libraries.
"""

from __future__ import annotations

import asyncio
import atexit
import json
import logging
import os
import signal
import sys
import time
from typing import Optional

from ai_interview.config import LOG_FILE, META_FILE, PID_FILE, Config

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# PID file helpers
# ---------------------------------------------------------------------------

def write_pid_file(pid: Optional[int] = None) -> None:
    pid = pid or os.getpid()
    PID_FILE.write_text(str(pid))


def remove_pid_file() -> None:
    try:
        PID_FILE.unlink(missing_ok=True)
    except Exception:
        pass


def read_pid() -> Optional[int]:
    try:
        return int(PID_FILE.read_text().strip())
    except Exception:
        return None


def is_process_running(pid: int) -> bool:
    try:
        os.kill(pid, 0)
        return True
    except (ProcessLookupError, PermissionError):
        return False


def kill_existing() -> bool:
    """Kill any running daemon. Returns True if a process was killed."""
    pid = read_pid()
    if pid is None:
        return False
    if not is_process_running(pid):
        remove_pid_file()
        return False
    try:
        os.kill(pid, signal.SIGTERM)
        for _ in range(50):
            time.sleep(0.1)
            if not is_process_running(pid):
                break
        else:
            os.kill(pid, signal.SIGKILL)
    except Exception:
        pass
    remove_pid_file()
    return True


# ---------------------------------------------------------------------------
# Metadata helpers (for `ai-interview status`)
# ---------------------------------------------------------------------------

def write_meta(config: Config, transcriber_name: str = "") -> None:
    meta = {
        "started_at": time.time(),
        "port": config.port,
        "pid": os.getpid(),
        "claude_model": config.claude_model,
        "transcriber": transcriber_name,
        "interview_language": config.interview_language,
        "challenge_type": config.challenge_type,
        "context_path": config.context_path,
    }
    META_FILE.write_text(json.dumps(meta))


def read_meta() -> Optional[dict]:
    try:
        return json.loads(META_FILE.read_text())
    except Exception:
        return None


def remove_meta() -> None:
    try:
        META_FILE.unlink(missing_ok=True)
    except Exception:
        pass


# ---------------------------------------------------------------------------
# daemonize: double-fork to detach from terminal
# ---------------------------------------------------------------------------

def daemonize() -> None:
    """Double-fork to create a true background daemon.

    Call this BEFORE importing any audio libraries.
    After this returns we are in the grandchild (daemon) process.
    """
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError as exc:
        sys.stderr.write(f"First fork failed: {exc}\n")
        sys.exit(1)

    os.setsid()

    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError as exc:
        sys.stderr.write(f"Second fork failed: {exc}\n")
        sys.exit(1)

    os.umask(0)
    os.chdir("/")

    LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    sys.stdout.flush()
    sys.stderr.flush()

    with open(os.devnull, "r") as f:
        os.dup2(f.fileno(), sys.stdin.fileno())
    with open(LOG_FILE, "a") as f:
        os.dup2(f.fileno(), sys.stdout.fileno())
        os.dup2(f.fileno(), sys.stderr.fileno())


# ---------------------------------------------------------------------------
# Main daemon entrypoint
# ---------------------------------------------------------------------------

def run_daemon(config: Config) -> None:
    """Entry point for the daemon process.

    Called from the subprocess spawned by `ai-interview start --daemon`.
    All audio / Deepgram imports happen here, safely after daemonize().
    """
    daemonize()

    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        handlers=[logging.StreamHandler()],
    )
    logger.info("Daemon started (pid=%d)", os.getpid())

    write_pid_file()
    atexit.register(remove_pid_file)
    atexit.register(remove_meta)

    # Hide from macOS Dock
    try:
        from AppKit import NSApplication, NSApplicationActivationPolicyProhibited
        NSApplication.sharedApplication().setActivationPolicy_(
            NSApplicationActivationPolicyProhibited
        )
    except Exception:
        pass

    # Auto-terminate after timeout
    def _alarm_handler(signum, frame):
        logger.info("Auto-terminate timeout reached")
        sys.exit(0)

    signal.signal(signal.SIGALRM, _alarm_handler)
    signal.alarm(config.timeout_hours * 3600)

    shutdown_flag = {"stop": False}

    def _term_handler(signum, frame):
        logger.info("SIGTERM received, shutting down")
        shutdown_flag["stop"] = True

    signal.signal(signal.SIGTERM, _term_handler)

    # -----------------------------------------------------------------------
    # NOW safe to import audio and network libraries
    # -----------------------------------------------------------------------
    from ai_interview.buffer import RollingTranscriptBuffer, ScreenshotBuffer
    from ai_interview.state import state
    from ai_interview.audio.capture import CombinedAudioCapture
    from ai_interview.audio.transcriber import create_transcriber
    from ai_interview.screenshot import ensure_screenshots_dir
    from ai_interview.server.app import create_app
    from ai_interview.hotkeys import start_hotkey_listener

    ensure_screenshots_dir()

    state.started_at = time.time()
    state.transcript_buffer = RollingTranscriptBuffer(config.buffer_minutes)
    state.screenshot_buffer = ScreenshotBuffer()

    # Start audio capture
    audio_capture = CombinedAudioCapture(sample_rate=16000)
    try:
        audio_capture.start()
        state.audio_active = True
        logger.info("Audio capture started")
    except Exception as exc:
        logger.error("Audio capture failed: %s — continuing without audio", exc)

    # Start transcriber (Deepgram preferred, Whisper fallback)
    transcriber = create_transcriber(
        audio_capture=audio_capture,
        transcript_buffer=state.transcript_buffer,
        deepgram_api_key=config.deepgram_api_key,
        transcription_language=config.transcription_language,
    )
    if config.deepgram_api_key:
        transcriber_name = "Deepgram"
    elif transcriber is not None:
        transcriber_name = "Whisper (local)"
    else:
        transcriber_name = "none (screenshots only)"
    write_meta(config, transcriber_name=transcriber_name)
    logger.info("Transcriber: %s", transcriber_name)

    start_hotkey_listener(config)

    async def _main():
        import aiohttp.web as web

        loop = asyncio.get_running_loop()
        state.asyncio_loop = loop
        state.shutdown_event = asyncio.Event()

        app = create_app(config)
        runner = web.AppRunner(app)
        await runner.setup()
        site = web.TCPSite(runner, "0.0.0.0", config.port)
        await site.start()
        logger.info("Viewer server on 0.0.0.0:%d", config.port)

        # Periodic health check — keeps the event loop responsive
        while not shutdown_flag["stop"]:
            await asyncio.sleep(0.5)
            # Log a heartbeat every 5 minutes so we can detect stalls
            if int(time.time()) % 300 < 1:
                logger.info("Daemon heartbeat — uptime %ds", int(time.time() - state.started_at))

        await runner.cleanup()

    try:
        asyncio.run(_main())
    except Exception as exc:
        logger.error("Event loop error: %s", exc)
    finally:
        if transcriber is not None:
            transcriber.stop()
        audio_capture.stop()
        # Clean up all screenshots captured during this session
        try:
            from ai_interview.config import SCREENSHOTS_DIR
            deleted = 0
            for f in SCREENSHOTS_DIR.glob("*.jpg"):
                f.unlink(missing_ok=True)
                deleted += 1
            if deleted:
                logger.info("Cleaned up %d screenshot(s)", deleted)
        except Exception as exc:
            logger.warning("Screenshot cleanup failed: %s", exc)
        logger.info("Daemon exited cleanly")
