use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;

/// random.org returns timestamps in a non-RFC3339 form: "YYYY-MM-DD HH:MM:SSZ".
/// Some endpoints (e.g. usage) actually return RFC3339, so we accept both.
pub mod rorg_datetime {
    use super::*;

    pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(d)?;
        parse(&s).map_err(serde::de::Error::custom)
    }

    pub fn deserialize_opt<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: Option<String> = Option::deserialize(d)?;
        match s {
            None => Ok(None),
            Some(s) => parse(&s).map(Some).map_err(serde::de::Error::custom),
        }
    }

    fn parse(s: &str) -> Result<DateTime<Utc>, String> {
        if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
            return Ok(dt.with_timezone(&Utc));
        }
        let trimmed = s.trim_end_matches('Z');
        if let Ok(naive) = NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%d %H:%M:%S") {
            return Ok(Utc.from_utc_datetime(&naive));
        }
        if let Ok(naive) = NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%dT%H:%M:%S") {
            return Ok(Utc.from_utc_datetime(&naive));
        }
        Err(format!("unparseable timestamp: {s}"))
    }
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BlobFormat {
    #[default]
    Base64,
    Hex,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TicketType {
    Singleton,
    Head,
    Tail,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum IntOrArray {
    One(i64),
    Many(Vec<i64>),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BoolOrArray {
    One(bool),
    Many(Vec<bool>),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeqValues<T> {
    Uniform(Vec<T>),
    Multiform(Vec<Vec<T>>),
}

/// Identifies an existing pre-generated randomization, used to make repeatable draws.
/// One of `date` (YYYY-MM-DD) or `id` (string).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Pregenerated {
    Date { date: String },
    Id { id: String },
}

/// Wraps a basic random.org response (top-level `random` field).
/// The contents of `data` are method-specific.
#[derive(Debug, Clone, Deserialize)]
pub struct BasicRandom<T> {
    pub data: T,
    #[serde(rename = "completionTime", with = "rorg_datetime")]
    pub completion_time: DateTime<Utc>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct BasicResult<T> {
    pub random: BasicRandom<T>,
    #[serde(rename = "bitsUsed")]
    pub bits_used: u64,
    #[serde(rename = "bitsLeft")]
    pub bits_left: u64,
    #[serde(rename = "requestsLeft")]
    pub requests_left: u64,
    #[serde(rename = "advisoryDelay")]
    pub advisory_delay: u64,
}

/// The signed `random` payload — kept verbatim as JSON because every byte must be
/// preserved exactly for signature verification.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SignedRandom(pub Value);

#[derive(Debug, Clone, Deserialize)]
pub struct SignedResult {
    pub random: SignedRandom,
    pub signature: String,
    #[serde(rename = "bitsUsed")]
    pub bits_used: u64,
    #[serde(rename = "bitsLeft")]
    pub bits_left: u64,
    #[serde(rename = "requestsLeft")]
    pub requests_left: u64,
    #[serde(rename = "advisoryDelay")]
    pub advisory_delay: u64,
}

impl SignedResult {
    pub fn data<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
        let data = self.random.0.get("data").cloned().unwrap_or(Value::Null);
        serde_json::from_value(data)
    }

    pub fn serial_number(&self) -> Option<u64> {
        self.random.0.get("serialNumber").and_then(|v| v.as_u64())
    }
}

#[derive(Debug, Clone, Deserialize)]
pub struct UsageResult {
    pub status: String,
    #[serde(rename = "creationTime", with = "rorg_datetime")]
    pub creation_time: DateTime<Utc>,
    #[serde(rename = "bitsLeft")]
    pub bits_left: u64,
    #[serde(rename = "requestsLeft")]
    pub requests_left: u64,
    #[serde(rename = "totalBits")]
    pub total_bits: i64,
    #[serde(rename = "totalRequests")]
    pub total_requests: i64,
}

#[derive(Debug, Clone, Deserialize)]
pub struct VerifyResult {
    pub authenticity: bool,
}

#[derive(Debug, Clone, Deserialize)]
pub struct CreateTicketsResult(pub Vec<TicketStub>);

/// Returned by `createTickets`. Newly created tickets are unused, so they
/// only carry id + creation time + chain links.
#[derive(Debug, Clone, Deserialize)]
pub struct TicketStub {
    #[serde(rename = "ticketId")]
    pub ticket_id: String,
    #[serde(rename = "creationTime", with = "rorg_datetime")]
    pub creation_time: DateTime<Utc>,
    #[serde(rename = "nextTicketId")]
    pub next_ticket_id: Option<String>,
    #[serde(rename = "previousTicketId")]
    pub previous_ticket_id: Option<String>,
}

/// Full ticket detail returned by `getTicket` / `listTickets`.
#[derive(Debug, Clone, Deserialize)]
pub struct Ticket {
    #[serde(rename = "ticketId")]
    pub ticket_id: String,
    #[serde(rename = "hashedApiKey")]
    pub hashed_api_key: String,
    #[serde(rename = "showResult", default)]
    pub show_result: Option<bool>,
    #[serde(rename = "creationTime", with = "rorg_datetime")]
    pub creation_time: DateTime<Utc>,
    #[serde(
        rename = "expirationTime",
        default,
        deserialize_with = "rorg_datetime::deserialize_opt"
    )]
    pub expiration_time: Option<DateTime<Utc>>,
    #[serde(
        rename = "usedTime",
        default,
        deserialize_with = "rorg_datetime::deserialize_opt"
    )]
    pub used_time: Option<DateTime<Utc>>,
    #[serde(rename = "serialNumber")]
    pub serial_number: Option<u64>,
    #[serde(rename = "previousTicketId")]
    pub previous_ticket_id: Option<String>,
    #[serde(rename = "nextTicketId")]
    pub next_ticket_id: Option<String>,
    pub result: Option<Value>,
}
