use std::collections::BTreeMap;
use std::io::{BufRead, Write};

use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};

use crate::runner::report::RunnerWarnings;

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ClientMessage {
    Spawn {
        argv: Vec<String>,
        env: BTreeMap<String, String>,
    },
    Stdin {
        data: String,
    },
    Stop,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum KeeperStatus {
    Ready { warnings: RunnerWarnings },
    Error { message: String },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum KeeperMessage {
    ChildSpawned,
    ChildStdout {
        data: Vec<u8>,
    },
    ChildStderr {
        data: Vec<u8>,
    },
    ChildExited,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum KeeperFrame {
    KeeperStatus {
        status: KeeperStatus,
    },
    KeeperMessage {
        message: KeeperMessage,
    },
}


impl KeeperStatus {
    pub fn ready(warnings: RunnerWarnings) -> Self {
        Self::Ready { warnings }
    }

    pub fn error(message: impl Into<String>) -> Self {
        Self::Error {
            message: message.into(),
        }
    }

    pub fn warnings(&self) -> Option<&RunnerWarnings> {
        match self {
            Self::Ready { warnings } => Some(warnings),
            Self::Error { .. } => None,
        }
    }

    pub fn error_message(&self) -> Option<&str> {
        match self {
            Self::Ready { .. } => None,
            Self::Error { message } => Some(message),
        }
    }
}

impl KeeperMessage {
    pub fn child_stdout(data: Vec<u8>) -> Self {
        Self::ChildStdout { data }
    }

    pub fn child_stderr(data: Vec<u8>) -> Self {
        Self::ChildStderr { data }
    }

    pub fn output_bytes(&self) -> Option<&[u8]> {
        match self {
            Self::ChildStdout { data } | Self::ChildStderr { data } => Some(data),
            Self::ChildSpawned | Self::ChildExited => None,
        }
    }

    pub fn is_child_exited(&self) -> bool {
        matches!(self, Self::ChildExited)
    }
}

impl KeeperFrame {
    pub fn status(status: KeeperStatus) -> Self {
        Self::KeeperStatus { status }
    }

    pub fn child(message: KeeperMessage) -> Self {
        Self::KeeperMessage { message }
    }
}

pub fn write_json_line<W, T>(writer: &mut W, message: &T) -> Result<()>
where
    W: Write,
    T: Serialize,
{
    let mut data = serde_json::to_vec(message)?;
    data.push(b'\n');
    writer.write_all(&data)?;
    writer.flush()?;
    Ok(())
}

pub fn read_json_line<R, T>(reader: &mut R) -> Result<T>
where
    R: BufRead,
    T: serde::de::DeserializeOwned,
{
    let mut line = String::new();
    let size = reader.read_line(&mut line)?;
    if size == 0 {
        bail!("runner stream closed");
    }
    Ok(serde_json::from_str(
        line.trim_end_matches(['\n', '\r']),
    )?)
}

#[cfg(test)]
mod tests {
    use std::io::{BufReader, Cursor};

    use super::{KeeperFrame, KeeperMessage, read_json_line, write_json_line};

    #[test]
    fn child_stderr_round_trip_preserves_raw_bytes_and_stream() {
        let expected = vec![0xff, 0x00, b'\n'];
        let frame = KeeperFrame::child(KeeperMessage::child_stderr(expected.clone()));
        let mut encoded = Vec::new();

        write_json_line(&mut encoded, &frame).expect("frame should serialize");
        let decoded: KeeperFrame = read_json_line(&mut BufReader::new(Cursor::new(encoded)))
            .expect("frame should deserialize");

        match decoded {
            KeeperFrame::KeeperMessage {
                message: KeeperMessage::ChildStderr { data },
            } => assert_eq!(data, expected),
            _ => panic!("frame should remain child stderr"),
        }
    }

    #[test]
    fn child_stdout_and_stderr_remain_distinct() {
        assert!(matches!(
            KeeperMessage::child_stdout(Vec::new()),
            KeeperMessage::ChildStdout { .. }
        ));
        assert!(matches!(
            KeeperMessage::child_stderr(Vec::new()),
            KeeperMessage::ChildStderr { .. }
        ));
    }

    #[test]
    fn closed_stream_is_reported_as_an_error() {
        let result = read_json_line::<_, KeeperFrame>(&mut BufReader::new(Cursor::new(
            Vec::new(),
        )));

        assert_eq!(
            result.expect_err("empty stream should fail").to_string(),
            "runner stream closed"
        );
    }
}
