use std::collections::HashMap;
use std::fmt;
use std::io;
use std::time::Duration;

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

use super::{util, Asciicast, Event, EventData, Header, Version};
use crate::tty::TtyTheme;

#[derive(Deserialize)]
struct V2Header {
    version: u8,
    width: u16,
    height: u16,
    timestamp: Option<u64>,
    idle_time_limit: Option<f64>,
    command: Option<String>,
    title: Option<String>,
    env: Option<HashMap<String, Option<String>>>,
    theme: Option<V2Theme>,
}

#[derive(Deserialize, Serialize, Clone)]
struct V2Theme {
    #[serde(deserialize_with = "deserialize_color")]
    fg: RGB8,
    #[serde(deserialize_with = "deserialize_color")]
    bg: RGB8,
    #[serde(deserialize_with = "deserialize_palette")]
    palette: V2Palette,
}

#[derive(Clone)]
struct RGB8(rgb::RGB8);

#[derive(Clone)]
struct V2Palette(Vec<RGB8>);

#[derive(Debug, Deserialize)]
struct V2Event {
    #[serde(deserialize_with = "util::deserialize_time")]
    time: Duration,
    #[serde(deserialize_with = "deserialize_code")]
    code: V2EventCode,
    data: String,
}

#[derive(PartialEq, Debug)]
enum V2EventCode {
    Output,
    Input,
    Resize,
    Marker,
    Other(char),
}

pub struct Parser(V2Header);

pub fn open(header_line: &str) -> Result<Parser> {
    let header = serde_json::from_str::<V2Header>(header_line)?;

    if header.version != 2 {
        bail!("not an asciicast v2 file")
    }

    Ok(Parser(header))
}

impl Parser {
    pub fn parse<'a, I: Iterator<Item = io::Result<String>> + Send + 'a>(
        self,
        lines: I,
    ) -> Asciicast<'a> {
        let term_type = self
            .0
            .env
            .as_ref()
            .map(|env| env.get("TERM").cloned())
            .unwrap_or_default()
            .unwrap_or_default();

        let term_theme = self.0.theme.as_ref().map(|t| t.into());

        let env = self.0.env.map(|env| {
            env.into_iter()
                .filter_map(|(k, v)| v.map(|v| (k, v)))
                .collect()
        });

        let header = Header {
            term_cols: self.0.width,
            term_rows: self.0.height,
            term_type,
            term_version: None,
            term_theme,
            timestamp: self.0.timestamp,
            idle_time_limit: self.0.idle_time_limit,
            command: self.0.command.clone(),
            title: self.0.title.clone(),
            env,
        };

        let events = Box::new(lines.filter_map(parse_line));

        Asciicast {
            version: Version::Two,
            header,
            events,
        }
    }
}

fn parse_line(line: io::Result<String>) -> Option<Result<Event>> {
    match line {
        Ok(line) => {
            if line.is_empty() {
                None
            } else {
                Some(parse_event(line))
            }
        }

        Err(e) => Some(Err(e.into())),
    }
}

fn parse_event(line: String) -> Result<Event> {
    let event = serde_json::from_str::<V2Event>(&line).context("asciicast v2 parse error")?;

    let data = match event.code {
        V2EventCode::Output => EventData::Output(event.data),
        V2EventCode::Input => EventData::Input(event.data),

        V2EventCode::Resize => match event.data.split_once('x') {
            Some((cols, rows)) => {
                let cols: u16 = cols
                    .parse()
                    .map_err(|e| anyhow!("invalid cols value in resize event: {e}"))?;

                let rows: u16 = rows
                    .parse()
                    .map_err(|e| anyhow!("invalid rows value in resize event: {e}"))?;

                EventData::Resize(cols, rows)
            }

            None => {
                bail!("invalid size value in resize event");
            }
        },

        V2EventCode::Marker => EventData::Marker(event.data),
        V2EventCode::Other(c) => EventData::Other(c, event.data),
    };

    Ok(Event {
        time: event.time,
        data,
    })
}

fn deserialize_code<'de, D>(deserializer: D) -> Result<V2EventCode, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;
    use V2EventCode::*;

    let value: &str = Deserialize::deserialize(deserializer)?;

    match value {
        "o" => Ok(Output),
        "i" => Ok(Input),
        "r" => Ok(Resize),
        "m" => Ok(Marker),
        "" => Err(Error::custom("missing event code")),
        s => Ok(Other(s.chars().next().unwrap())),
    }
}

pub struct V2Encoder {
    time_offset: Duration,
}

impl V2Encoder {
    pub fn new(time_offset: Duration) -> Self {
        Self { time_offset }
    }

    pub fn header(&mut self, header: &Header) -> Vec<u8> {
        let header: V2Header = header.into();
        let mut data = serde_json::to_string(&header).unwrap().into_bytes();
        data.push(b'\n');

        data
    }

    pub fn event(&mut self, event: &Event) -> Vec<u8> {
        let mut data = self.serialize_event(event).into_bytes();
        data.push(b'\n');

        data
    }

    fn serialize_event(&self, event: &Event) -> String {
        use EventData::*;

        let (code, data) = match &event.data {
            Output(data) => ('o', self.to_json_string(data)),
            Input(data) => ('i', self.to_json_string(data)),
            Resize(cols, rows) => ('r', self.to_json_string(&format!("{cols}x{rows}"))),
            Marker(data) => ('m', self.to_json_string(data)),
            Exit(data) => ('x', self.to_json_string(&data.to_string())),
            Other(code, data) => (*code, self.to_json_string(data)),
        };

        format!(
            "[{}, {}, {}]",
            format_time(event.time + self.time_offset),
            self.to_json_string(&code.to_string()),
            data,
        )
    }

    fn to_json_string(&self, s: &str) -> String {
        serde_json::to_string(s).unwrap()
    }
}

fn format_time(time: Duration) -> String {
    let time = time.as_micros();
    let mut formatted_time = format!("{}.{:0>6}", time / 1_000_000, time % 1_000_000);
    let dot_idx = formatted_time.find('.').unwrap();

    for idx in (dot_idx + 2..=formatted_time.len() - 1).rev() {
        if formatted_time.as_bytes()[idx] != b'0' {
            break;
        }

        formatted_time.truncate(idx);
    }

    formatted_time
}

impl serde::Serialize for V2Header {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeMap;

        let mut len = 4;

        if self.timestamp.is_some() {
            len += 1;
        }

        if self.idle_time_limit.is_some() {
            len += 1;
        }

        if self.command.is_some() {
            len += 1;
        }

        if self.title.is_some() {
            len += 1;
        }

        if self.env.as_ref().is_some_and(|env| !env.is_empty()) {
            len += 1;
        }

        if self.theme.is_some() {
            len += 1;
        }

        let mut map = serializer.serialize_map(Some(len))?;
        map.serialize_entry("version", &2)?;
        map.serialize_entry("width", &self.width)?;
        map.serialize_entry("height", &self.height)?;

        if let Some(timestamp) = self.timestamp {
            map.serialize_entry("timestamp", &timestamp)?;
        }

        if let Some(limit) = self.idle_time_limit {
            map.serialize_entry("idle_time_limit", &limit)?;
        }

        if let Some(command) = &self.command {
            map.serialize_entry("command", &command)?;
        }

        if let Some(title) = &self.title {
            map.serialize_entry("title", &title)?;
        }

        if let Some(env) = &self.env {
            if !env.is_empty() {
                map.serialize_entry("env", &env)?;
            }
        }

        if let Some(theme) = &self.theme {
            map.serialize_entry("theme", &theme)?;
        }

        map.end()
    }
}

fn deserialize_color<'de, D>(deserializer: D) -> Result<RGB8, D::Error>
where
    D: Deserializer<'de>,
{
    let value: &str = Deserialize::deserialize(deserializer)?;
    parse_hex_color(value).ok_or(serde::de::Error::custom("invalid hex triplet"))
}

fn parse_hex_color(rgb: &str) -> Option<RGB8> {
    if rgb.len() != 7 {
        return None;
    }

    let r = u8::from_str_radix(&rgb[1..3], 16).ok()?;
    let g = u8::from_str_radix(&rgb[3..5], 16).ok()?;
    let b = u8::from_str_radix(&rgb[5..7], 16).ok()?;

    Some(RGB8(rgb::RGB8::new(r, g, b)))
}

fn deserialize_palette<'de, D>(deserializer: D) -> Result<V2Palette, D::Error>
where
    D: Deserializer<'de>,
{
    let value: &str = Deserialize::deserialize(deserializer)?;
    let mut colors: Vec<RGB8> = value.split(':').filter_map(parse_hex_color).collect();
    let len = colors.len();

    if len == 8 {
        colors.extend_from_within(..);
    } else if len != 16 {
        return Err(serde::de::Error::custom("expected 8 or 16 hex triplets"));
    }

    Ok(V2Palette(colors))
}

impl serde::Serialize for RGB8 {
    fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl fmt::Display for RGB8 {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
        write!(f, "#{:0>2x}{:0>2x}{:0>2x}", self.0.r, self.0.g, self.0.b)
    }
}

impl serde::Serialize for V2Palette {
    fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let palette = self
            .0
            .iter()
            .map(|c| c.to_string())
            .collect::<Vec<_>>()
            .join(":");

        serializer.serialize_str(&palette)
    }
}

impl From<&Header> for V2Header {
    fn from(header: &Header) -> Self {
        let env = header
            .env
            .clone()
            .map(|env| env.into_iter().map(|(k, v)| (k, Some(v))).collect());

        V2Header {
            version: 2,
            width: header.term_cols,
            height: header.term_rows,
            timestamp: header.timestamp,
            idle_time_limit: header.idle_time_limit,
            command: header.command.clone(),
            title: header.title.clone(),
            env,
            theme: header.term_theme.as_ref().map(|t| t.into()),
        }
    }
}

impl From<&TtyTheme> for V2Theme {
    fn from(tty_theme: &TtyTheme) -> Self {
        let palette = tty_theme.palette.iter().copied().map(RGB8).collect();

        V2Theme {
            fg: RGB8(tty_theme.fg),
            bg: RGB8(tty_theme.bg),
            palette: V2Palette(palette),
        }
    }
}

impl From<&V2Theme> for TtyTheme {
    fn from(tty_theme: &V2Theme) -> Self {
        let palette = tty_theme.palette.0.iter().map(|c| c.0).collect();

        TtyTheme {
            fg: tty_theme.fg.0,
            bg: tty_theme.bg.0,
            palette,
        }
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    #[test]
    fn format_time() {
        assert_eq!(super::format_time(Duration::from_micros(0)), "0.0");
        assert_eq!(
            super::format_time(Duration::from_micros(1000001)),
            "1.000001"
        );
        assert_eq!(super::format_time(Duration::from_micros(12300000)), "12.3");
        assert_eq!(
            super::format_time(Duration::from_micros(12000003)),
            "12.000003"
        );
    }
}
