import json
import os
import signal
import queue
import threading
import time

import numpy as np
import sounddevice as sd

from .backend import WtypeBackend
from .commands import process_and_output
from .compressor import Compressor
from .model import SAMPLE_RATE, load_model, transcribe_audio
from .vad import (
    DEFAULT_SILENCE_FRAMES,
    FRAME_SIZE,
    VADStateMachine,
    calibrate_silence,
    describe_input_device,
    pause_seconds_to_frames,
    resample_to_target,
    resolve_input_rate,
)

STATE_DIR = os.path.expanduser("~/.local/state/cohere")
STATE_FILE = os.path.join(STATE_DIR, "state.json")
LOG_FILE = os.path.join(STATE_DIR, "daemon.log")


def _write_state(pid: int, status: str):
    os.makedirs(STATE_DIR, exist_ok=True)
    with open(STATE_FILE, "w") as f:
        json.dump({"pid": pid, "status": status, "started_at": time.time()}, f)


_backend = WtypeBackend()


def read_state() -> dict | None:
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return None


def is_running() -> bool:
    state = read_state()
    if state is None:
        return False
    pid = state.get("pid")
    if pid is None:
        return False
    try:
        os.kill(pid, 0)
        return True
    except OSError:
        return False


def stop_daemon() -> bool:
    state = read_state()
    if state is None:
        return False
    pid = state.get("pid")
    if pid is None:
        return False
    try:
        os.kill(pid, signal.SIGTERM)
        for _ in range(20):
            time.sleep(0.1)
            try:
                os.kill(pid, 0)
            except OSError:
                break
        _write_state(pid, "stopped")
        return True
    except OSError:
        _write_state(pid, "stopped")
        return False


def run_daemon(language: str = "en", pause: float | None = None, device=None, normalize: bool = False):
    pid = os.getpid()
    _write_state(pid, "starting")

    def handle_sigterm(signum, frame):
        raise KeyboardInterrupt

    signal.signal(signal.SIGTERM, handle_sigterm)

    silence_frames = pause_seconds_to_frames(pause) if pause else DEFAULT_SILENCE_FRAMES
    processor, model = load_model()
    print(f"Using input device: {describe_input_device(device)}")
    comp = Compressor() if normalize else None
    if comp:
        print("  Normalization: compressor+limiter enabled")
    threshold = calibrate_silence(device=device, compressor=comp)
    capture_rate = resolve_input_rate(device)
    capture_blocksize = FRAME_SIZE * capture_rate // SAMPLE_RATE
    vad = VADStateMachine(threshold, silence_frames=silence_frames)
    seg_queue: queue.Queue = queue.Queue()
    stop_event = threading.Event()
    start_time = time.monotonic()

    _write_state(pid, "running")

    def transcription_worker():
        while not stop_event.is_set() or not seg_queue.empty():
            try:
                _seg_start, audio = seg_queue.get(timeout=0.5)
            except queue.Empty:
                continue
            text = transcribe_audio(processor, model, audio, language)
            text = text.strip()
            if text:
                process_and_output(text, _backend)

    worker = threading.Thread(target=transcription_worker, daemon=True)
    worker.start()

    def audio_callback(indata, frames, time_info, status):
        if stop_event.is_set():
            return
        elapsed = time.monotonic() - start_time
        frame = resample_to_target(indata[:, 0].copy(), capture_rate)
        if comp is not None:
            frame = comp.process(frame)
        result = vad.process_frame(frame, elapsed)
        if result is not None:
            seg_queue.put(result)

    stream = sd.InputStream(
        samplerate=capture_rate, channels=1, dtype="float32",
        callback=audio_callback, blocksize=capture_blocksize, device=device,
    )

    try:
        with stream:
            while True:
                time.sleep(0.1)
    except KeyboardInterrupt:
        pass

    stop_event.set()

    if vad.speaking and vad.segment:
        seg_queue.put((vad.segment_start_time, np.concatenate(vad.segment)))

    worker.join(timeout=30)
    _write_state(pid, "stopped")
