use anyhow::Result;
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::path::PathBuf;
use std::sync::mpsc;

use super::common::Project;

/// Unified event stream: output JSONL events from all (or one) task log files.
/// With --follow, watches for new events in real-time.
/// type_filter: comma-separated event type names (e.g. "step_finished,step_yielded")
pub fn run(task_filter: Option<&str>, follow: bool, type_filter: Option<&str>) -> Result<()> {
    let project = Project::load()?;
    let logs_dir = project.pawl_dir.join("logs");

    if !logs_dir.exists() {
        if follow {
            // Create dir so watcher has something to watch
            std::fs::create_dir_all(&logs_dir)?;
        } else {
            return Ok(());
        }
    }

    // Resolve task filter
    let task_filter = task_filter
        .map(|t| project.resolve_task_name(t))
        .transpose()?;

    // Discover existing log files
    let log_files = discover_log_files(&logs_dir, task_filter.as_deref())?;

    // Parse type filter into a set
    let type_set: Option<Vec<&str>> = type_filter
        .map(|f| f.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect());

    // Print existing events (sorted by timestamp across files)
    let mut file_offsets: HashMap<PathBuf, u64> = HashMap::new();
    for (task_name, path) in &log_files {
        let offset = print_events_from_file(task_name, path, 0, type_set.as_deref())?;
        file_offsets.insert(path.clone(), offset);
    }

    if !follow {
        return Ok(());
    }

    // Watch for changes
    let (tx, rx) = mpsc::channel();

    let mut watcher = RecommendedWatcher::new(
        move |res: notify::Result<notify::Event>| {
            if let Ok(event) = res {
                let _ = tx.send(event);
            }
        },
        Config::default(),
    )?;

    watcher.watch(&logs_dir, RecursiveMode::NonRecursive)?;

    // Poll for events
    while let Ok(event) = rx.recv() {
        if !matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) {
            continue;
        }

        for path in &event.paths {
            let Some(ext) = path.extension() else {
                continue;
            };
            if ext != "jsonl" {
                continue;
            }

            let task_name = path
                .file_stem()
                .and_then(|s| s.to_str())
                .unwrap_or("")
                .to_string();

            // Apply task filter
            if let Some(ref filter) = task_filter
                && task_name != *filter {
                    continue;
                }

            let offset = file_offsets.get(path).copied().unwrap_or(0);
            if let Ok(new_offset) = print_events_from_file(&task_name, path, offset, type_set.as_deref()) {
                file_offsets.insert(path.clone(), new_offset);
            }
        }
    }

    Ok(())
}

/// Discover JSONL log files, optionally filtered by task name.
fn discover_log_files(
    logs_dir: &std::path::Path,
    task_filter: Option<&str>,
) -> Result<Vec<(String, PathBuf)>> {
    let mut files = Vec::new();

    if !logs_dir.exists() {
        return Ok(files);
    }

    for entry in std::fs::read_dir(logs_dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.extension().and_then(|s| s.to_str()) != Some("jsonl") {
            continue;
        }

        let task_name = path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("")
            .to_string();

        if let Some(filter) = task_filter
            && task_name != filter {
                continue;
            }

        files.push((task_name, path));
    }

    files.sort_by(|a, b| a.0.cmp(&b.0));
    Ok(files)
}

/// Print new JSONL events from a file starting at the given byte offset.
/// Each line is prefixed with a "name" field injected into the JSON.
/// If type_filter is Some, only prints events whose "type" matches.
/// Returns the new byte offset after reading.
fn print_events_from_file(task_name: &str, path: &std::path::Path, offset: u64, type_filter: Option<&[&str]>) -> Result<u64> {
    let file = std::fs::File::open(path)?;
    let metadata = file.metadata()?;
    let file_len = metadata.len();

    if file_len <= offset {
        return Ok(offset);
    }

    let mut reader = BufReader::new(file);
    reader.seek(SeekFrom::Start(offset))?;

    let mut current_pos = offset;

    loop {
        let mut line = String::new();
        let bytes_read = reader.read_line(&mut line)?;
        if bytes_read == 0 {
            break;
        }
        current_pos += bytes_read as u64;

        let line = line.trim();
        if line.is_empty() {
            continue;
        }

        // Apply type filter if present
        if let Some(types) = type_filter {
            // Extract "type":"..." from JSON line
            if let Some(type_val) = extract_json_type(line)
                && !types.contains(&type_val) {
                    continue;
                }
        }

        // Inject "name" field into the JSON object
        if let Some(rest) = line.strip_prefix('{') {
            println!("{{\"name\":\"{}\",{}", task_name, rest);
        } else {
            println!("{}", line);
        }
    }

    Ok(current_pos)
}

/// Extract the "type" value from a JSON line without full parsing.
/// Looks for `"type":"value"` pattern.
fn extract_json_type(line: &str) -> Option<&str> {
    let marker = "\"type\":\"";
    let start = line.find(marker)? + marker.len();
    let end = start + line[start..].find('"')?;
    Some(&line[start..end])
}
