use crate::{
    config::Config,
    events::{LogEvent, parse_line},
    kokoro::KokoroClient,
};
use anyhow::Result;
use rodio::{Decoder, OutputStreamBuilder, Sink};
use std::{
    collections::{HashMap, VecDeque},
    fs::File,
    io::{BufRead, BufReader, Cursor, Seek, SeekFrom},
    path::{Path, PathBuf},
    sync::{
        Arc,
        atomic::{AtomicUsize, Ordering},
        mpsc::{self, Receiver, Sender},
    },
    thread,
    time::{Duration, Instant},
};
use tokio::sync::mpsc as tokio_mpsc;

#[derive(Debug, Clone)]
pub enum WorkerEvent {
    Log(LogEvent),
    Activity(String),
    KokoroStatus(KokoroStatus),
    Voices(Vec<String>),
    QueueDepth(usize),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KokoroStatus {
    Checking,
    Connected,
    Unreachable,
}

#[derive(Debug)]
pub struct SynthesisJob {
    pub text: String,
    pub voice: String,
    pub speed: f32,
    pub repeat_key: String,
    pub observed_at: Instant,
}

#[derive(Debug)]
struct RepeatState {
    last_seen: Instant,
    count: usize,
}

#[derive(Debug)]
pub enum AudioCommand {
    Play(Vec<u8>),
    SetVolume(f32),
}

pub fn spawn_log_tailer(
    path: PathBuf,
    sender: Sender<WorkerEvent>,
    generation: Arc<AtomicUsize>,
    expected_generation: usize,
) {
    thread::spawn(move || {
        let mut position = None;
        while generation.load(Ordering::Relaxed) == expected_generation {
            match read_new_lines(&path, &mut position) {
                Ok(lines) => {
                    for line in lines {
                        if let Some(event) = parse_line(&line) {
                            let _ = sender.send(WorkerEvent::Log(event));
                        }
                    }
                }
                Err(error) => {
                    let _ = sender.send(WorkerEvent::Activity(format!("log watch: {error}")));
                }
            }
            thread::sleep(Duration::from_millis(350));
        }
    });
}

fn read_new_lines(path: &Path, position: &mut Option<u64>) -> Result<Vec<String>> {
    let mut file = File::open(path)?;
    let length = file.metadata()?.len();
    let offset = position.unwrap_or(length);
    let offset = if length < offset { 0 } else { offset };
    file.seek(SeekFrom::Start(offset))?;
    let reader = BufReader::new(&file);
    let lines = reader.lines().collect::<std::io::Result<Vec<_>>>()?;
    *position = Some(length);
    Ok(lines)
}

pub fn spawn_audio_worker(volume: f32, event_sender: Sender<WorkerEvent>) -> Sender<AudioCommand> {
    let (sender, receiver): (Sender<AudioCommand>, Receiver<AudioCommand>) = mpsc::channel();
    thread::spawn(move || {
        let stream = match OutputStreamBuilder::open_default_stream() {
            Ok(stream) => stream,
            Err(error) => {
                let _ = event_sender.send(WorkerEvent::Activity(format!(
                    "audio output unavailable: {error}"
                )));
                return;
            }
        };
        let mut volume = volume;
        while let Ok(command) = receiver.recv() {
            let wav = match command {
                AudioCommand::Play(wav) => wav,
                AudioCommand::SetVolume(updated_volume) => {
                    volume = updated_volume;
                    continue;
                }
            };
            let _ = event_sender.send(WorkerEvent::QueueDepth(1));
            match Decoder::try_from(Cursor::new(wav)) {
                Ok(source) => {
                    let sink = Sink::connect_new(stream.mixer());
                    sink.set_volume(volume);
                    sink.append(source);
                    sink.sleep_until_end();
                }
                Err(error) => {
                    let _ = event_sender.send(WorkerEvent::Activity(format!(
                        "audio decode failed: {error}"
                    )));
                }
            }
            let _ = event_sender.send(WorkerEvent::QueueDepth(0));
        }
    });
    sender
}

pub fn spawn_kokoro_worker(
    config: Config,
    mut jobs: tokio_mpsc::Receiver<SynthesisJob>,
    audio_sender: Sender<AudioCommand>,
    event_sender: Sender<WorkerEvent>,
) -> tokio_mpsc::Sender<Config> {
    let (control_sender, mut control_receiver) = tokio_mpsc::channel::<Config>(4);
    tokio::spawn(async move {
        let mut config = config;
        let mut client = match KokoroClient::new(&config.kokoro_url) {
            Ok(client) => client,
            Err(error) => {
                let _ = event_sender.send(WorkerEvent::Activity(format!("Kokoro config: {error}")));
                return;
            }
        };
        let mut voices = Vec::new();
        let mut last_health_check = Instant::now() - Duration::from_secs(30);
        let mut repeats = HashMap::<String, RepeatState>::new();
        let mut queued = VecDeque::new();

        loop {
            while let Ok(updated_config) = control_receiver.try_recv() {
                match KokoroClient::new(&updated_config.kokoro_url) {
                    Ok(updated_client) => {
                        config = updated_config;
                        client = updated_client;
                        voices.clear();
                        last_health_check = Instant::now() - Duration::from_secs(30);
                        let _ = event_sender.send(WorkerEvent::Activity(
                            "Kokoro settings updated; validating endpoint…".into(),
                        ));
                    }
                    Err(error) => {
                        let _ = event_sender.send(WorkerEvent::Activity(format!(
                            "Kokoro endpoint rejected: {error}"
                        )));
                    }
                }
            }

            if last_health_check.elapsed() >= Duration::from_secs(5) {
                last_health_check = Instant::now();
                let _ = event_sender.send(WorkerEvent::KokoroStatus(KokoroStatus::Checking));
                match client.health().await {
                    Ok(()) => match client.voices().await {
                        Ok(found) => {
                            if found != voices {
                                voices = found.clone();
                                let _ = event_sender.send(WorkerEvent::Voices(found));
                            }
                            let _ = event_sender
                                .send(WorkerEvent::KokoroStatus(KokoroStatus::Connected));
                        }
                        Err(error) => {
                            let _ = event_sender
                                .send(WorkerEvent::Activity(format!("voice list failed: {error}")));
                            let _ = event_sender
                                .send(WorkerEvent::KokoroStatus(KokoroStatus::Unreachable));
                        }
                    },
                    Err(_) => {
                        let _ =
                            event_sender.send(WorkerEvent::KokoroStatus(KokoroStatus::Unreachable));
                    }
                }
            }

            while let Ok(job) = jobs.try_recv() {
                if queued.len() >= config.max_queue_length {
                    let _ = event_sender.send(WorkerEvent::Activity(
                        "speech queue full; line dropped".into(),
                    ));
                } else {
                    let mut job = job;
                    job.speed =
                        repeat_speed(&mut repeats, &job.repeat_key, &config, job.observed_at);
                    queued.push_back(job);
                }
            }

            if let Some(job) = queued.pop_front() {
                let _ = event_sender.send(WorkerEvent::QueueDepth(queued.len() + 1));
                match client
                    .synthesize_wav(&job.text, &job.voice, job.speed)
                    .await
                {
                    Ok(wav) => {
                        if audio_sender.send(AudioCommand::Play(wav)).is_err() {
                            let _ = event_sender.send(WorkerEvent::Activity(
                                "audio worker stopped; unable to play speech".into(),
                            ));
                        }
                    }
                    Err(error) => {
                        let _ = event_sender.send(WorkerEvent::Activity(format!(
                            "Kokoro speech failed: {error}"
                        )));
                    }
                }
                let _ = event_sender.send(WorkerEvent::QueueDepth(queued.len()));
            }

            tokio::time::sleep(Duration::from_millis(100)).await;
        }
    });
    control_sender
}

fn repeat_speed(
    repeats: &mut HashMap<String, RepeatState>,
    key: &str,
    config: &Config,
    now: Instant,
) -> f32 {
    let window = Duration::from_secs(config.repeat_window_seconds);
    let state = repeats.entry(key.to_owned()).or_insert(RepeatState {
        last_seen: now,
        count: 0,
    });
    if now.duration_since(state.last_seen) >= window {
        state.count = 0;
    }
    state.last_seen = now;
    state.count += 1;

    match state.count {
        1 => 1.0,
        2 => config.repeat_second_speed.clamp(0.25, 4.0),
        _ => config.repeat_third_plus_speed.clamp(0.25, 4.0),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::tempdir;

    #[test]
    fn tailer_starts_at_end_and_recovers_after_truncation() {
        let directory = tempdir().unwrap();
        let path = directory.path().join("eqlog.txt");
        fs::write(&path, "old\n").unwrap();
        let mut position = None;
        assert!(read_new_lines(&path, &mut position).unwrap().is_empty());
        fs::write(&path, "old\nnew\n").unwrap();
        assert_eq!(read_new_lines(&path, &mut position).unwrap(), vec!["new"]);
        fs::write(&path, "fresh\n").unwrap();
        assert_eq!(read_new_lines(&path, &mut position).unwrap(), vec!["fresh"]);
    }

    #[test]
    fn repeated_dialogue_speeds_up_then_resets() {
        let config = Config::default();
        let now = Instant::now();
        let mut repeats = HashMap::new();

        assert_eq!(
            repeat_speed(&mut repeats, "npc:turga\u{1f}hello", &config, now),
            1.0
        );
        assert_eq!(
            repeat_speed(
                &mut repeats,
                "npc:turga\u{1f}hello",
                &config,
                now + Duration::from_secs(1),
            ),
            1.5
        );
        assert_eq!(
            repeat_speed(
                &mut repeats,
                "npc:turga\u{1f}hello",
                &config,
                now + Duration::from_secs(2),
            ),
            2.0
        );
        assert_eq!(
            repeat_speed(
                &mut repeats,
                "npc:turga\u{1f}hello",
                &config,
                now + Duration::from_secs(18),
            ),
            1.0
        );
    }
}
