use crate::events::Event;
use crate::pool::Pool;
use crate::renderer;
use crate::terminal::Terminal;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use tokio::io::{
    AsyncBufReadExt,
    AsyncReadExt,
    AsyncWriteExt,
    BufReader,
};
use tokio::net::UnixListener;
use tokio::sync::broadcast;

pub struct AppState {
    pub pool: Mutex<Pool>,
    pub event_tx: broadcast::Sender<Event>,
    pub config: Mutex<Config>,
}

pub struct Config {
    pub idle_timeout_ms: u64,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            idle_timeout_ms: 2000,
        }
    }
}

pub async fn run(socket_path: &str, idle_timeout_ms: u64) {
    let _ = std::fs::remove_file(socket_path);
    let listener = UnixListener::bind(socket_path).unwrap();

    let (event_tx, _) = broadcast::channel::<Event>(256);
    let config = Config {
        idle_timeout_ms,
        ..Config::default()
    };

    let state = Arc::new(AppState {
        pool: Mutex::new(Pool::new()),
        event_tx,
        config: Mutex::new(config),
    });

    eprintln!("t4a daemon listening on {socket_path}");

    loop {
        let (stream, _) =
            listener.accept().await.unwrap();
        let state_ = state.clone();
        tokio::spawn(async move {
            handle_conn(state_, stream).await;
        });
    }
}

async fn handle_conn(
    state: Arc<AppState>,
    stream: tokio::net::UnixStream,
) {
    let (reader, mut writer) = stream.into_split();
    let mut reader = BufReader::new(reader);
    let mut line = String::new();

    if reader.read_line(&mut line).await.is_err()
        || line.is_empty()
    {
        return;
    }

    let req: Value = match serde_json::from_str(&line) {
        Ok(v) => v,
        Err(e) => {
            let _ = write_json(
                &mut writer,
                &err_json(&e.to_string()),
            )
            .await;
            return;
        }
    };

    let cmd = req["cmd"].as_str().unwrap_or("");

    match cmd {
        "create" => {
            let resp = handle_create(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        "list" => {
            let resp = handle_list(&state);
            let _ = write_json(&mut writer, &resp).await;
        }
        "kill" => {
            let resp = handle_kill(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        "send" => {
            let resp = handle_send(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        "text" => {
            let resp = handle_text(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        "cursor" => {
            let resp = handle_cursor(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        "resize" => {
            let resp = handle_resize(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        "screenshot" => {
            handle_screen(&state, &req, &mut writer).await;
        }
        "events" => {
            handle_events(&state, &req, &mut writer).await;
        }
        "attach" => {
            handle_attach(
                &state, &req, reader, &mut writer,
            )
            .await;
        }
        "config" => {
            let resp = handle_config(&state, &req);
            let _ = write_json(&mut writer, &resp).await;
        }
        _ => {
            let resp = err_json("unknown command");
            let _ = write_json(&mut writer, &resp).await;
        }
    }
}

fn err_json(msg: &str) -> Value {
    serde_json::json!({"ok": false, "error": msg})
}

fn ok_json() -> Value {
    serde_json::json!({"ok": true})
}

async fn write_json(
    w: &mut tokio::net::unix::OwnedWriteHalf,
    v: &Value,
) -> std::io::Result<()> {
    let mut buf = serde_json::to_vec(v).unwrap();
    buf.push(b'\n');
    w.write_all(&buf).await
}

fn get_term(
    state: &AppState,
    req: &Value,
) -> Result<Terminal, Value> {
    let id = req["id"].as_str().unwrap_or("");
    let pool = state.pool.lock().unwrap();
    pool.get(id)
        .cloned()
        .ok_or_else(|| err_json("not found"))
}

// --- Handlers ---

#[derive(Deserialize)]
struct CreateReq {
    cols: Option<u16>,
    rows: Option<u16>,
    cmd_args: Option<Vec<String>>,
    cwd: Option<String>,
    env: Option<HashMap<String, String>>,
}

fn handle_create(state: &AppState, req: &Value) -> Value {
    let r: CreateReq =
        match serde_json::from_value(req.clone()) {
            Ok(v) => v,
            Err(e) => return err_json(&e.to_string()),
        };
    let cols = r.cols.unwrap_or(80);
    let rows = r.rows.unwrap_or(24);
    let cmd = r.cmd_args.unwrap_or_default();
    let env = r.env.unwrap_or_default();

    let id = state.pool.lock().unwrap().next_id();

    let idle_timeout = {
        let cfg = state.config.lock().unwrap();
        Duration::from_millis(cfg.idle_timeout_ms)
    };

    match Terminal::spawn(
        id.clone(),
        cols,
        rows,
        cmd,
        r.cwd,
        env,
        state.event_tx.clone(),
        idle_timeout,
    ) {
        Ok(term) => {
            let pid = term.pid();
            state.pool.lock().unwrap().insert(term);
            serde_json::json!({
                "ok": true,
                "id": id,
                "cols": cols,
                "rows": rows,
                "pid": pid,
            })
        }
        Err(e) => err_json(&e.to_string()),
    }
}

fn handle_list(state: &AppState) -> Value {
    let pool = state.pool.lock().unwrap();
    let terminals: Vec<Value> = pool
        .list()
        .iter()
        .map(|t| {
            let (rows, cols) =
                t.with_screen(|s| s.size());
            serde_json::json!({
                "id": t.id(),
                "cols": cols,
                "rows": rows,
                "pid": t.pid(),
                "alive": t.alive(),
                "title": t.title(),
            })
        })
        .collect();
    serde_json::json!({"ok": true, "terminals": terminals})
}

fn handle_kill(state: &AppState, req: &Value) -> Value {
    let id = req["id"].as_str().unwrap_or("");
    let mut pool = state.pool.lock().unwrap();
    if let Some(term) = pool.remove(id) {
        term.kill();
        ok_json()
    } else {
        err_json("not found")
    }
}

fn handle_send(state: &AppState, req: &Value) -> Value {
    let term = match get_term(state, req) {
        Ok(t) => t,
        Err(e) => return e,
    };
    let data = if let Some(input) = req["input"].as_str() {
        input.as_bytes().to_vec()
    } else if let Some(b64) = req["input_base64"].as_str() {
        use base64::Engine;
        base64::engine::general_purpose::STANDARD
            .decode(b64)
            .unwrap_or_default()
    } else {
        return err_json("missing input");
    };
    term.send_input(&data);
    ok_json()
}

fn handle_text(state: &AppState, req: &Value) -> Value {
    let term = match get_term(state, req) {
        Ok(t) => t,
        Err(e) => return e,
    };
    let trim = req["trim"].as_bool().unwrap_or(true);

    term.with_screen(|screen| {
        let (rows, cols) = screen.size();
        let total = rows as i64;
        // Indexed from bottom: 0 = last line.
        // 0:5 = last 5 lines. Omit range = all.
        let start = req["start"].as_i64().unwrap_or(0);
        let end = req["end"]
            .as_i64()
            .unwrap_or(total);
        let top_start =
            (total - end).max(0).min(total);
        let top_end =
            (total - start).max(0).min(total);

        let mut lines = Vec::new();
        for row in top_start..top_end {
            let mut line = String::new();
            for col in 0..cols {
                if let Some(cell) =
                    screen.cell(row as u16, col)
                {
                    let s = cell.contents();
                    if s.is_empty() {
                        line.push(' ');
                    } else {
                        line.push_str(s);
                    }
                }
            }
            if trim {
                line = line.trim_end().to_string();
            }
            lines.push(line);
        }

        serde_json::json!({
            "ok": true,
            "lines": lines,
            "region": "viewport",
            "start": start,
            "end": end,
            "total_lines": total,
        })
    })
}

fn handle_cursor(state: &AppState, req: &Value) -> Value {
    let term = match get_term(state, req) {
        Ok(t) => t,
        Err(e) => return e,
    };
    term.with_screen(|screen| {
        let (row, col) = screen.cursor_position();
        serde_json::json!({
            "ok": true,
            "row": row,
            "col": col,
            "visible": !screen.hide_cursor(),
        })
    })
}

fn handle_resize(state: &AppState, req: &Value) -> Value {
    let term = match get_term(state, req) {
        Ok(t) => t,
        Err(e) => return e,
    };
    let cols = req["cols"].as_u64().unwrap_or(80) as u16;
    let rows = req["rows"].as_u64().unwrap_or(24) as u16;
    term.resize(rows, cols);
    ok_json()
}

async fn handle_screen(
    state: &AppState,
    req: &Value,
    writer: &mut tokio::net::unix::OwnedWriteHalf,
) {
    let term = match get_term(state, req) {
        Ok(t) => t,
        Err(e) => {
            let _ = write_json(writer, &e).await;
            return;
        }
    };
    let cursor =
        req["cursor"].as_bool().unwrap_or(true);
    let pad =
        req["pad"].as_u64().unwrap_or(1) as u32;
    let scale =
        req["scale"].as_u64().unwrap_or(66) as u32;

    let png = term.with_screen(|s| {
        renderer::render_screen(s, cursor, pad, scale)
    });

    let header = serde_json::json!({
        "ok": true, "len": png.len()
    });
    if write_json(writer, &header).await.is_err() {
        return;
    }
    let _ = writer.write_all(&png).await;
}

async fn handle_events(
    state: &AppState,
    req: &Value,
    writer: &mut tokio::net::unix::OwnedWriteHalf,
) {
    let filter_id =
        req["terminal"].as_str().map(|s| s.to_string());
    let mut rx = state.event_tx.subscribe();

    loop {
        match rx.recv().await {
            Ok(event) => {
                if let Some(ref fid) = filter_id {
                    if event.terminal_id() != fid {
                        continue;
                    }
                }
                let mut buf =
                    serde_json::to_vec(&event).unwrap();
                buf.push(b'\n');
                if writer.write_all(&buf).await.is_err()
                {
                    break;
                }
            }
            Err(_) => break,
        }
    }
}

fn handle_config(state: &AppState, req: &Value) -> Value {
    let mut cfg = state.config.lock().unwrap();
    if let Some(ms) = req["idle_timeout_ms"].as_u64() {
        cfg.idle_timeout_ms = ms;
    }
    ok_json()
}

async fn handle_attach(
    state: &AppState,
    req: &Value,
    mut reader: BufReader<
        tokio::net::unix::OwnedReadHalf,
    >,
    writer: &mut tokio::net::unix::OwnedWriteHalf,
) {
    let term = match get_term(state, req) {
        Ok(t) => t,
        Err(e) => {
            let _ = write_json(writer, &e).await;
            return;
        }
    };

    let mut raw_rx = term.subscribe_raw();
    let mut buf = [0u8; 4096];

    loop {
        tokio::select! {
            result = raw_rx.recv() => {
                match result {
                    Ok(bytes) => {
                        if writer.write_all(&bytes)
                            .await.is_err()
                        {
                            break;
                        }
                    }
                    Err(_) => break,
                }
            }
            result = reader.read(&mut buf) => {
                match result {
                    Ok(0) | Err(_) => break,
                    Ok(n) => {
                        term.send_input(&buf[..n]);
                    }
                }
            }
        }
    }
}
