use redis::{FromRedisValue, RedisResult, ToRedisArgs, Value};
use serde::{Deserialize, Serialize};

#[derive(Serialize, PartialEq, Debug, Deserialize)]
pub enum SubscriptionChannel {
    Amadeus,
    Notifica,
    BajkomatApi,
    Shopify,
}

impl SubscriptionChannel {
    pub fn get_channel(&self) -> &'static str {
        match self {
            SubscriptionChannel::Amadeus => "AMADEUS_CHANNEL",
            SubscriptionChannel::Notifica => "NOTIFICA_CHANNEL",
            SubscriptionChannel::BajkomatApi => "BAJKOMAT_API_CHANNEL",
            SubscriptionChannel::Shopify => "SHOPIFY_CHANNEL",
        }
    }

    pub fn from_channel(value: &str) -> Option<Self> {
        [
            SubscriptionChannel::Amadeus,
            SubscriptionChannel::BajkomatApi,
            SubscriptionChannel::Notifica,
            SubscriptionChannel::Shopify,
        ]
        .into_iter()
        .find(|variant| variant.get_channel() == value)
    }
}

#[derive(Serialize, PartialEq, Debug, Deserialize)]
pub enum ListChannel {
    Amadeus,
    Notifica,
    BajkomatApi,
    KsefGpt,
    N8n,
    Shopify,
}

impl ListChannel {
    pub fn get_channel(&self) -> &'static str {
        match self {
            ListChannel::Amadeus => "AMADEUS_LIST",
            ListChannel::Notifica => "NOTIFICA_LIST",
            ListChannel::BajkomatApi => "BAJKOMAT_API_LIST",
            ListChannel::KsefGpt => "KSEF_GPT_LISt",
            ListChannel::Shopify => "SHOPIFY_LIST",
            ListChannel::N8n => "N_8_N",
        }
    }
    pub fn from_channel(value: &str) -> Option<Self> {
        [
            ListChannel::Amadeus,
            ListChannel::BajkomatApi,
            ListChannel::KsefGpt,
            ListChannel::Notifica,
            ListChannel::Shopify,
            ListChannel::N8n,
        ]
        .into_iter()
        .find(|variant| variant.get_channel() == value)
    }
}

pub enum CacheSpace {
    Amadeus,
    Notifica,
    BajkomatApi,
    Shopify,
    KsefGpt,
}

impl CacheSpace {
    pub fn get_space(&self) -> &'static str {
        match self {
            CacheSpace::Amadeus => "AMADEUS_CACHE",
            CacheSpace::Notifica => "NOTIFICA_CACHE",
            CacheSpace::BajkomatApi => "BAJKOMAT_API_CACHE",
            CacheSpace::Shopify => "SHOPIFY_CACHE",
            CacheSpace::KsefGpt => "KSEFGPT_CACHE",
        }
    }
}

#[derive(Serialize, PartialEq, Debug, Deserialize)]
pub struct ChannelMessage {
    pub meta: Option<MessageMeta>,
    pub channel: SubscriptionChannel,
    pub body: Option<serde_json::Value>,
}

#[derive(Serialize, PartialEq, Debug, Deserialize)]
pub struct MessageMeta {
    pub app_name: String,
}

#[derive(Serialize, PartialEq, Debug, Deserialize)]
pub struct ListMessage {
    pub meta: Option<MessageMeta>,
    pub body: Option<serde_json::Value>,
}

#[derive(Serialize, PartialEq, Debug, Deserialize)]
pub struct CacheValue<T> {
    pub meta: Option<MessageMeta>,
    pub value: Option<T>,
}

impl From<String> for SubscriptionChannel {
    fn from(value: String) -> Self {
        SubscriptionChannel::from_channel(&value).expect("Invalid channel string")
    }
}

impl FromRedisValue for ChannelMessage {
    fn from_redis_value(v: &Value) -> RedisResult<Self> {
        match v {
            Value::Array(items) => {
                if items.len() < 2 {
                    return Err((redis::ErrorKind::TypeError, "Not enough items").into());
                }
                let channel: String = redis::from_redis_value(&items[0])?;
                let mut body: Option<String> = None;
                for chunk in items[1..].chunks(2) {
                    if chunk.len() == 2 {
                        // let key: String = redis::from_redis_value(&chunk[0])?;
                        let val = redis::from_redis_value(&chunk[1])?;
                        body = Some(val)
                    }
                }

                Ok(ChannelMessage {
                    channel: SubscriptionChannel::from(channel),
                    // TODO to check if body is Some
                    meta: None,
                    body: serde_json::from_str(&body.unwrap()).unwrap(),
                })
            }
            _ => Err((redis::ErrorKind::TypeError, "Unexpected Redis value").into()),
        }
    }
}

impl ToRedisArgs for ChannelMessage {
    fn write_redis_args<W>(&self, out: &mut W)
    where
        W: ?Sized + redis::RedisWrite,
    {
        let msg = serde_json::to_vec(&self);
        if let Ok(message) = msg {
            out.write_arg(&message);
        }
    }
}
