use crate::relay::ws::{WsEvent, WsMessage};
use crate::{Error, Result};
use serde::de::{self, IgnoredAny, SeqAccess, Visitor};
use std::borrow::Cow;
use std::fmt;

#[derive(Debug, Eq, PartialEq)]
pub struct CommandResult<'a> {
    event_id: &'a str,
    status: bool,
    message: &'a str,
}

#[derive(Debug, Eq, PartialEq)]
pub enum RelayMessage<'a> {
    OK(CommandResult<'a>),
    Eose(&'a str),
    Event(&'a str, &'a str),
    Notice(&'a str),
    Closed(&'a str, &'a str),
    /// NIP-77 negentropy reconciliation message: ["NEG-MSG", <sub_id>, <payload>]
    NegMsg(Cow<'a, str>, Cow<'a, str>),
    /// NIP-77 negentropy error: ["NEG-ERR", <sub_id>, <reason>]
    NegErr(Cow<'a, str>, Cow<'a, str>),
}

#[derive(Debug)]
pub enum RelayEvent<'a> {
    Opened,
    Closed,
    Other(&'a WsMessage),
    Error(Error),
    Message(RelayMessage<'a>),
}

impl<'a> From<&'a WsEvent> for RelayEvent<'a> {
    fn from(event: &'a WsEvent) -> RelayEvent<'a> {
        match event {
            WsEvent::Opened(_) => RelayEvent::Opened,
            WsEvent::Closed => RelayEvent::Closed,
            WsEvent::Message(ref ws_msg) => ws_msg.into(),
            WsEvent::Error(s) => RelayEvent::Error(Error::Generic(s.to_owned())),
        }
    }
}

impl<'a> From<&'a WsMessage> for RelayEvent<'a> {
    fn from(wsmsg: &'a WsMessage) -> RelayEvent<'a> {
        match wsmsg {
            WsMessage::Text(s) => match RelayMessage::from_json(s).map(RelayEvent::Message) {
                Ok(msg) => msg,
                Err(err) => RelayEvent::Error(err),
            },
            wsmsg => RelayEvent::Other(wsmsg),
        }
    }
}

impl<'a> RelayMessage<'a> {
    pub fn eose(subid: &'a str) -> Self {
        RelayMessage::Eose(subid)
    }

    pub fn notice(msg: &'a str) -> Self {
        RelayMessage::Notice(msg)
    }

    pub fn ok(event_id: &'a str, status: bool, message: &'a str) -> Self {
        RelayMessage::OK(CommandResult {
            event_id,
            status,
            message,
        })
    }

    pub fn event(ev: &'a str, sub_id: &'a str) -> Self {
        RelayMessage::Event(sub_id, ev)
    }

    /// Construct a relay `CLOSED` message with its subscription id and reason.
    pub fn closed(sub_id: &'a str, message: &'a str) -> Self {
        RelayMessage::Closed(sub_id, message)
    }

    pub fn from_json(msg: &'a str) -> Result<RelayMessage<'a>> {
        if msg.is_empty() {
            return Err(Error::Empty);
        }

        // make sure we can inspect the begning of the message below ...
        if msg.len() < 12 {
            return Err(Error::DecodeFailed("message too short".into()));
        }

        // Notice
        // Relay response format: ["NOTICE", <message>]
        if msg.len() >= 12 && &msg[0..=9] == "[\"NOTICE\"," {
            // TODO: there could be more than one space, whatever
            let start = if msg.as_bytes().get(10).copied() == Some(b' ') {
                12
            } else {
                11
            };
            let end = msg.len() - 2;
            return Ok(Self::notice(&msg[start..end]));
        }

        // Event
        // Relay response format: ["EVENT", <subscription id>, <event JSON>]
        if &msg[0..=7] == "[\"EVENT\"" {
            let raw = &msg[9..];
            let trimmed = raw.trim_start_matches(' ');
            let start = msg.len() - trimmed.len();
            if let Some(comma_index) = msg[start..].find(',') {
                let subid_end = start + comma_index;
                let subid = &msg[start..subid_end].trim().trim_matches('"');
                return Ok(Self::event(msg, subid));
            } else {
                return Err(Error::DecodeFailed("Invalid EVENT format".into()));
            }
        }

        // EOSE (NIP-15)
        // Relay response format: ["EOSE", <subscription_id>]
        if &msg[0..=7] == "[\"EOSE\"," {
            let start = if msg.as_bytes().get(8).copied() == Some(b' ') {
                10 // Skip space after the comma
            } else {
                9 // Start immediately after the comma
            };

            // Use rfind to locate the last quote
            if let Some(end_bracket_index) = msg.rfind(']') {
                let end = end_bracket_index - 1; // Account for space before bracket
                if start < end {
                    // Trim subscription id and remove extra spaces and quotes
                    let subid = &msg[start..end].trim().trim_matches('"').trim();
                    return Ok(RelayMessage::eose(subid));
                }
            }
            return Err(Error::DecodeFailed(
                "Invalid subscription ID or format".into(),
            ));
        }

        // CLOSED (NIP-01)
        // Relay response format: ["CLOSED", <subscription_id>, <message>]
        if msg.starts_with("[\"CLOSED\"") {
            let parts: Vec<&'a str> =
                serde_json::from_str(msg).map_err(|err| Error::DecodeFailed(err.to_string()))?;
            if parts.len() != 3 || parts[0] != "CLOSED" {
                return Err(Error::DecodeFailed("Invalid CLOSED format".into()));
            }

            return Ok(Self::closed(parts[1], parts[2]));
        }

        // OK (NIP-20)
        // Relay response format: ["OK",<event_id>, <true|false>, <message>]
        if &msg[0..=5] == "[\"OK\"," {
            if msg.len() < 78 {
                return Err(Error::DecodeFailed("Invalid OK format".into()));
            }

            let event_id = &msg[7..71];
            let booly = &msg[73..77];
            let (status, idx) = if booly == "true" {
                (true, 77)
            } else if msg[73..].starts_with("false") {
                (false, 78)
            } else {
                return Err(Error::DecodeFailed("bad boolean value".into()));
            };

            if msg.as_bytes().get(idx).copied() != Some(b',') {
                return Err(Error::DecodeFailed("Invalid OK format".into()));
            }

            if msg.as_bytes().get(idx + 1).copied() != Some(b'"') {
                return Err(Error::DecodeFailed("Invalid OK format".into()));
            }

            if msg.as_bytes().get(msg.len() - 2).copied() != Some(b'"')
                || msg.as_bytes().last().copied() != Some(b']')
            {
                return Err(Error::DecodeFailed("Invalid OK format".into()));
            }

            let message = &msg[(idx + 2)..(msg.len() - 2)];

            return Ok(Self::ok(event_id, status, message));
        }

        if is_nip77_frame(msg) {
            return parse_nip77_frame(msg);
        }

        Err(Error::DecodeFailed(format!(
            "unrecognized message type: '{msg}'"
        )))
    }
}

/// Parse one NIP-77 relay frame as a JSON array and validate its arity.
fn parse_nip77_frame<'a>(msg: &'a str) -> Result<RelayMessage<'a>> {
    match serde_json::from_str::<Nip77Frame<'a>>(msg) {
        Ok(Nip77Frame::NegMsg(sub_id, payload)) => Ok(RelayMessage::NegMsg(sub_id, payload)),
        Ok(Nip77Frame::NegErr(sub_id, reason)) => Ok(RelayMessage::NegErr(sub_id, reason)),
        Err(err) => {
            let err = err.to_string();
            if err.contains("Invalid NEG-MSG format") {
                return Err(Error::DecodeFailed("Invalid NEG-MSG format".into()));
            }
            if err.contains("Invalid NEG-ERR format") {
                return Err(Error::DecodeFailed("Invalid NEG-ERR format".into()));
            }
            Err(Error::DecodeFailed(err))
        }
    }
}

fn is_nip77_frame(msg: &str) -> bool {
    matches!(first_json_array_command(msg), Some("NEG-MSG" | "NEG-ERR"))
}

fn first_json_array_command(msg: &str) -> Option<&str> {
    let msg = msg.trim_start();
    let msg = msg.strip_prefix('[')?.trim_start();
    let msg = msg.strip_prefix('"')?;
    let end = msg.find('"')?;
    Some(&msg[..end])
}

enum Nip77Frame<'a> {
    NegMsg(Cow<'a, str>, Cow<'a, str>),
    NegErr(Cow<'a, str>, Cow<'a, str>),
}

impl<'de> serde::Deserialize<'de> for Nip77Frame<'de> {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_seq(Nip77FrameVisitor)
    }
}

struct Nip77FrameVisitor;

impl<'de> Visitor<'de> for Nip77FrameVisitor {
    type Value = Nip77Frame<'de>;

    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("a relay JSON array")
    }

    fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
    where
        A: SeqAccess<'de>,
    {
        let Some(command) = seq.next_element::<&'de str>()? else {
            return Err(de::Error::custom("Invalid NIP-77 format"));
        };

        match command {
            "NEG-MSG" => parse_neg_msg_seq(&mut seq),
            "NEG-ERR" => parse_neg_err_seq(&mut seq),
            _ => Err(de::Error::custom("Invalid NIP-77 format")),
        }
    }
}

fn parse_neg_msg_seq<'de, A>(seq: &mut A) -> std::result::Result<Nip77Frame<'de>, A::Error>
where
    A: SeqAccess<'de>,
{
    let sub_id = next_required_string(seq, "Invalid NEG-MSG format")?;
    let payload = next_required_string(seq, "Invalid NEG-MSG format")?;
    if seq.next_element::<IgnoredAny>()?.is_some() {
        return Err(de::Error::custom("Invalid NEG-MSG format"));
    }

    Ok(Nip77Frame::NegMsg(sub_id, payload))
}

fn parse_neg_err_seq<'de, A>(seq: &mut A) -> std::result::Result<Nip77Frame<'de>, A::Error>
where
    A: SeqAccess<'de>,
{
    let sub_id = next_required_string(seq, "Invalid NEG-ERR format")?;
    let reason = next_required_string(seq, "Invalid NEG-ERR format")?;
    let _max_records = seq
        .next_element::<NegErrMaxRecords>()
        .map_err(|_| de::Error::custom("Invalid NEG-ERR format"))?;
    if seq.next_element::<IgnoredAny>()?.is_some() {
        return Err(de::Error::custom("Invalid NEG-ERR format"));
    }

    Ok(Nip77Frame::NegErr(sub_id, reason))
}

fn next_required_string<'de, A>(
    seq: &mut A,
    error: &'static str,
) -> std::result::Result<Cow<'de, str>, A::Error>
where
    A: SeqAccess<'de>,
{
    seq.next_element::<Cow<'de, str>>()
        .map_err(|_| de::Error::custom(error))?
        .ok_or_else(|| de::Error::custom(error))
}

struct NegErrMaxRecords;

impl<'de> serde::Deserialize<'de> for NegErrMaxRecords {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        <u64 as serde::Deserialize>::deserialize(deserializer)
            .map(|_| Self)
            .map_err(|_| de::Error::custom("Invalid NEG-ERR format"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_handle_various_messages() -> Result<()> {
        let tests = vec![
            // Valid cases
            (
                // shortest valid message
                r#"["EOSE","x"]"#,
                Ok(RelayMessage::eose("x")),
            ),
            (
                // also very short
                r#"["NOTICE",""]"#,
                Ok(RelayMessage::notice("")),
            ),
            (
                r#"["NOTICE","Invalid event format!"]"#,
                Ok(RelayMessage::notice("Invalid event format!")),
            ),
            (
                r#"["EVENT", "random_string", {"id":"example","content":"test"}]"#,
                Ok(RelayMessage::event(
                    r#"["EVENT", "random_string", {"id":"example","content":"test"}]"#,
                    "random_string",
                )),
            ),
            (
                r#"["EOSE","random-subscription-id"]"#,
                Ok(RelayMessage::eose("random-subscription-id")),
            ),
            (
                r#"["EOSE", "random-subscription-id"]"#,
                Ok(RelayMessage::eose("random-subscription-id")),
            ),
            (
                r#"["EOSE", "random-subscription-id" ]"#,
                Ok(RelayMessage::eose("random-subscription-id")),
            ),
            (
                r#"["CLOSED","sub1","error: shutting down idle subscription"]"#,
                Ok(RelayMessage::closed(
                    "sub1",
                    "error: shutting down idle subscription",
                )),
            ),
            (
                r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#,
                Ok(RelayMessage::ok(
                    "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",
                    true,
                    "pow: difficulty 25>=24",
                )),
            ),
            (
                r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",false,"contains comma, and escaped quote \"x\""]"#,
                Ok(RelayMessage::ok(
                    "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",
                    false,
                    r#"contains comma, and escaped quote \"x\""#,
                )),
            ),
            // Invalid cases
            (
                r#"["EVENT","random_string"]"#,
                Err(Error::DecodeFailed("Invalid EVENT format".into())),
            ),
            (
                r#"["EOSE"]"#,
                Err(Error::DecodeFailed("message too short".into())),
            ),
            (
                r#"["NOTICE"]"#,
                Err(Error::DecodeFailed("message too short".into())),
            ),
            (
                r#"["NOTICE": 404]"#,
                Err(Error::DecodeFailed(
                    "unrecognized message type: '[\"NOTICE\": 404]'".into(),
                )),
            ),
            (
                r#"["OK","event_id"]"#,
                Err(Error::DecodeFailed("Invalid OK format".into())),
            ),
            (
                r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#,
                Err(Error::DecodeFailed("Invalid OK format".into())),
            ),
            (
                r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#,
                Err(Error::DecodeFailed("bad boolean value".into())),
            ),
            (
                r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,404]"#,
                Err(Error::DecodeFailed("bad boolean value".into())),
            ),
            (
                r#"["CLOSED","sub1"]"#,
                Err(Error::DecodeFailed("Invalid CLOSED format".into())),
            ),
            // NEG-MSG (NIP-77)
            (
                r#"["NEG-MSG","neg-sub-1","abcdef0123"]"#,
                Ok(RelayMessage::NegMsg(
                    "neg-sub-1".into(),
                    "abcdef0123".into(),
                )),
            ),
            (
                r#"[ "NEG-MSG", "neg-sub-1", "abcdef0123" ]"#,
                Ok(RelayMessage::NegMsg(
                    "neg-sub-1".into(),
                    "abcdef0123".into(),
                )),
            ),
            // NEG-ERR (NIP-77)
            (
                r#"["NEG-ERR","neg-sub-1","RESULTS_TOO_BIG"]"#,
                Ok(RelayMessage::NegErr(
                    "neg-sub-1".into(),
                    "RESULTS_TOO_BIG".into(),
                )),
            ),
            (
                r#"[ "NEG-ERR", "neg-sub-1", "RESULTS_TOO_BIG" ]"#,
                Ok(RelayMessage::NegErr(
                    "neg-sub-1".into(),
                    "RESULTS_TOO_BIG".into(),
                )),
            ),
            (
                r#"["NEG-ERR","neg-sub-1","blocked: too many records",1000]"#,
                Ok(RelayMessage::NegErr(
                    "neg-sub-1".into(),
                    "blocked: too many records".into(),
                )),
            ),
            (
                r#"["NEG-ERR","neg-sub-1","blocked: \"too broad\""]"#,
                Ok(RelayMessage::NegErr(
                    "neg-sub-1".into(),
                    r#"blocked: "too broad""#.into(),
                )),
            ),
            // Invalid NEG-MSG
            (
                r#"["NEG-MSG","sub1"]"#,
                Err(Error::DecodeFailed("Invalid NEG-MSG format".into())),
            ),
            // Invalid NEG-ERR
            (
                r#"["NEG-ERR","neg-sub-1","blocked: too many records",1000,"extra"]"#,
                Err(Error::DecodeFailed("Invalid NEG-ERR format".into())),
            ),
            (
                r#"["NEG-ERR","neg-sub-1","blocked: too many records","1000"]"#,
                Err(Error::DecodeFailed("Invalid NEG-ERR format".into())),
            ),
        ];

        for (input, expected) in tests {
            match expected {
                Ok(expected_msg) => {
                    let result = RelayMessage::from_json(input);
                    assert_eq!(
                        result?, expected_msg,
                        "Expected {:?} for input: {}",
                        expected_msg, input
                    );
                }
                Err(expected_err) => {
                    let result = RelayMessage::from_json(input);
                    assert!(
                        matches!(result, Err(ref e) if e.to_string() == expected_err.to_string()),
                        "Expected error {:?} for input: {}, but got: {:?}",
                        expected_err,
                        input,
                        result
                    );
                }
            }
        }
        Ok(())
    }

    /*
    #[test]
    fn test_handle_valid_event() -> Result<()> {
        use tracing::debug;

        let valid_event_msg = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#;

        let id = "70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5";
        let pubkey = "379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe";
        let created_at = 1612809991;
        let kind = 1;
        let tags = vec![];
        let content = "test";
        let sig = "273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502";

        let handled_event = Note::new_dummy(id, pubkey, created_at, kind, tags, content, sig).expect("ev");
        debug!("event {:?}", handled_event);

        let msg = RelayMessage::from_json(valid_event_msg).expect("valid json");
        debug!("msg {:?}", msg);

        let note_json = serde_json::to_string(&handled_event).expect("json ev");

        assert_eq!(
            msg,
            RelayMessage::event(&note_json, "random_string")
        );

        Ok(())
    }

    #[test]
    fn test_handle_invalid_event() {
        //Mising Event field
        let invalid_event_msg = r#"["EVENT","random_string"]"#;
        //Event JSON with incomplete content
        let invalid_event_msg_content = r#"["EVENT","random_string",{"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"}]"#;

        assert!(matches!(
            RelayMessage::from_json(invalid_event_msg).unwrap_err(),
            Error::DecodeFailed
        ));

        assert!(matches!(
            RelayMessage::from_json(invalid_event_msg_content).unwrap_err(),
            Error::DecodeFailed
        ));
    }
    */

    // TODO: fix these tests
    /*
    #[test]
    fn test_handle_invalid_eose() {
        // Missing subscription ID
        assert!(matches!(
            RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(),
            Error::DecodeFailed
        ));

        // The subscription ID is not string
        assert!(matches!(
            RelayMessage::from_json(r#"["EOSE",404]"#).unwrap_err(),
            Error::DecodeFailed
        ));
    }

    #[test]
    fn test_handle_valid_ok() -> Result<()> {
        let valid_ok_msg = r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#;
        let handled_valid_ok_msg = RelayMessage::ok(
            "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",
            true,
            "pow: difficulty 25>=24".into(),
        );

        assert_eq!(RelayMessage::from_json(valid_ok_msg)?, handled_valid_ok_msg);

        Ok(())
    }
    */
}
