use crate::DecisionEntryType;
use crate::days_to_expire::DaysToExpire;
use crate::email::Email;
use crate::kind::DecisionListKind;
use crate::sdk::decision_list::http::requests::create::{CreateEntryRequest, UpdateEntryRequest};
use crate::sdk::endpoints::Endpoints;
use crate::sdk::endpoints::Endpoints::{
    CreateDecisionListEntryV1, DeleteDecisionListEntryV1, FetchDecisionListEntryV1,
    UpdateDecisionListEntryV1,
};
use crate::tax_id::CnpjOrCpf;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;

pub mod create;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecisionListEntryRequest {
    pub kind: DecisionListKind,
    #[serde(rename = "type")]
    pub entry_type: DecisionEntryType,
    pub value: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub days_to_expire: Option<DaysToExpire>,
}

impl DecisionListEntryRequest {
    pub fn new(
        kind: DecisionListKind,
        entry_type: DecisionEntryType,
        value: impl Into<String>,
        days_to_expire: Option<DaysToExpire>,
    ) -> Self {
        Self {
            kind,
            entry_type,
            value: value.into(),
            days_to_expire,
        }
    }

    pub fn email(
        kind: DecisionListKind,
        email: Email,
        days_to_expire: Option<DaysToExpire>,
    ) -> Self {
        Self::new(kind, DecisionEntryType::Email, email, days_to_expire)
    }

    pub fn tax_id(
        kind: DecisionListKind,
        tax_id: CnpjOrCpf,
        days_to_expire: Option<DaysToExpire>,
    ) -> Self {
        Self::new(kind, DecisionEntryType::TaxId, tax_id, days_to_expire)
    }

    pub fn ip(kind: DecisionListKind, ip: IpAddr, days_to_expire: Option<DaysToExpire>) -> Self {
        Self::new(kind, DecisionEntryType::Ip, ip.to_string(), days_to_expire)
    }

    pub fn create_endpoint(&self) -> Endpoints {
        CreateDecisionListEntryV1(self.kind, self.entry_type.clone())
    }

    pub fn update_endpoint(&self) -> Endpoints {
        UpdateDecisionListEntryV1(self.kind, self.entry_type.clone(), self.value.clone())
    }

    pub fn fetch_endpoint(&self) -> Endpoints {
        FetchDecisionListEntryV1(self.kind, self.entry_type.clone(), self.value.clone())
    }

    pub fn delete_endpoint(&self) -> Endpoints {
        DeleteDecisionListEntryV1(self.kind, self.entry_type.clone(), self.value.clone())
    }

    pub(crate) fn create_payload(&self) -> CreateEntryRequest {
        CreateEntryRequest {
            value: self.value.clone(),
        }
    }

    pub(crate) fn update_expire_days_payload(
        &self,
        days_to_expire: DaysToExpire,
    ) -> UpdateEntryRequest {
        UpdateEntryRequest { days_to_expire }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::DecisionEntryType;
    use crate::kind::DecisionListKind;
    use crate::sdk::decision_list::http::requests::DecisionListEntryRequest;
    use crate::tax_id::CnpjOrCpf;
    use std::net::IpAddr;

    #[test]
    fn test_create_generic_request() {
        let req = DecisionListEntryRequest::new(
            DecisionListKind::Blocklist,
            DecisionEntryType::Email,
            "test@example.com",
            None,
        );

        assert_eq!(req.kind, DecisionListKind::Blocklist);
        assert_eq!(req.entry_type, DecisionEntryType::Email);
        assert_eq!(req.value, "test@example.com");
        assert!(req.days_to_expire.is_none());
    }

    #[test]
    fn test_create_valid_email() {
        let req = DecisionListEntryRequest::email(
            DecisionListKind::Allowlist,
            Email::try_from("valid@email.com").expect("never fails here"),
            Some(DaysToExpire::new(30).unwrap()),
        );

        assert_eq!(req.kind, DecisionListKind::Allowlist);
        assert_eq!(req.entry_type, DecisionEntryType::Email);
        assert_eq!(req.value, "valid@email.com");
        assert_eq!(req.days_to_expire.unwrap().value(), 30);
    }

    #[test]
    fn test_create_ip() {
        let req = DecisionListEntryRequest::ip(
            DecisionListKind::Reviewlist,
            IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 1)),
            None,
        );

        assert_eq!(req.kind, DecisionListKind::Reviewlist);
        assert_eq!(req.entry_type, DecisionEntryType::Ip);
        assert_eq!(req.value, "192.168.1.1");
    }

    #[test]
    fn test_serialization() {
        let req = DecisionListEntryRequest::new(
            DecisionListKind::Blocklist,
            DecisionEntryType::Email,
            "serialize@test.com",
            Some(DaysToExpire::new(7).unwrap()),
        );

        let json = serde_json::to_value(&req).unwrap();

        assert_eq!(json["kind"], "blacklist");
        assert_eq!(json["type"], "email");
        assert_eq!(json["value"], "serialize@test.com");
        assert_eq!(json["days_to_expire"], 7);
    }

    #[test]
    fn test_serialization_without_expiration() {
        let req = DecisionListEntryRequest::tax_id(
            DecisionListKind::Blocklist,
            CnpjOrCpf::try_from("69221944034").expect("never fails here"),
            None,
        );

        let json = serde_json::to_value(&req).unwrap();

        assert_eq!(json["value"], "69221944034");
        assert!(json.get("days_to_expire").is_none());
    }
}
