// Copyright (c) 2021 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use crate::LustreCollectorError;
use std::{fmt, ops::Deref};

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
/// The hostname cooresponding to these stats.
pub struct Host(pub String);

impl Deref for Host {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
/// The Lustre target cooresponding to these stats.
pub struct Target(pub String);

impl Deref for Target {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
/// The name of the stat.
pub struct Param(pub String);

impl Deref for Param {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct ReqsStat {
    pub samples: i64,
    pub unit: String,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct BytesStat {
    pub samples: i64,
    pub unit: String,
    pub min: i64,
    pub max: i64,
    pub sum: i64,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct JobStatsOst {
    pub job_stats: Option<Vec<JobStatOst>>,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct JobStatOst {
    pub job_id: String,
    pub snapshot_time: i64,
    pub read_bytes: BytesStat,
    pub write_bytes: BytesStat,
    pub getattr: ReqsStat,
    pub setattr: ReqsStat,
    pub punch: ReqsStat,
    pub sync: ReqsStat,
    pub destroy: ReqsStat,
    pub create: ReqsStat,
    pub statfs: ReqsStat,
    pub get_info: ReqsStat,
    pub set_info: ReqsStat,
    pub quotactl: ReqsStat,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct JobStatsMdt {
    pub job_stats: Option<Vec<JobStatMdt>>,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct JobStatMdt {
    pub job_id: String,
    pub snapshot_time: i64,
    pub open: BytesStat,
    pub close: BytesStat,
    pub mknod: BytesStat,
    pub link: BytesStat,
    pub unlink: BytesStat,
    pub mkdir: BytesStat,
    pub rmdir: BytesStat,
    pub rename: BytesStat,
    pub getattr: BytesStat,
    pub setattr: BytesStat,
    pub getxattr: BytesStat,
    pub setxattr: BytesStat,
    pub statfs: BytesStat,
    pub sync: BytesStat,
    pub samedir_rename: BytesStat,
    pub crossdir_rename: BytesStat,
    pub read_bytes: BytesStat,
    pub write_bytes: BytesStat,
    pub punch: BytesStat,
}

pub mod lnet_exports {
    use std::collections::HashMap;

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct LocalNiS {
        pub nid: String,
        pub status: String,
        pub statistics: LNetStatistics,
        pub sent_stats: Stats,
        pub received_stats: Stats,
        pub dropped_stats: Stats,
        #[serde(rename = "health stats")]
        pub health_stats: HealthStats,
        pub tunables: Tunables,
        #[serde(rename = "dev cpt")]
        pub dev_cpt: i64,
        #[serde(rename = "CPT")]
        pub cpt: String,
        pub interfaces: Option<HashMap<i64, String>>,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct Stats {
        pub put: i64,
        pub get: i64,
        pub reply: i64,
        pub ack: i64,
        pub hello: i64,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct HealthStats {
        #[serde(rename = "health value")]
        health_value: i64,
        interrupts: i64,
        dropped: i64,
        aborted: i64,
        #[serde(rename = "no route")]
        no_route: i64,
        timeouts: i64,
        error: i64,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct HealthStatsPeer {
        #[serde(rename = "health value")]
        health_value: i64,
        dropped: i64,
        timeout: i64,
        error: i64,
        #[serde(rename = "network timeout")]
        network_timeout: i64,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct Net {
        #[serde(rename = "net type")]
        pub net_type: String,
        #[serde(rename = "local NI(s)")]
        pub local_nis: Vec<LocalNiS>,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct Global {
        numa_range: Option<i64>,
        max_intf: i64,
        discovery: i64,
        drop_asym_route: i64,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct Peer {
        #[serde(rename = "primary nid")]
        pub primary_nid: String,
        #[serde(rename = "Multi-Rail")]
        pub multi_rail: String,
        #[serde(rename = "peer ni")]
        pub peer_ni: Vec<PeerNi>,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct PeerNi {
        nid: String,
        state: String,
        max_ni_tx_credits: i64,
        available_tx_credits: i64,
        min_tx_credits: i64,
        tx_q_num_of_buf: i64,
        available_rtr_credits: i64,
        min_rtr_credits: i64,
        refcount: i64,
        statistics: LNetStatistics,
        sent_stats: Stats,
        received_stats: Stats,
        dropped_stats: Stats,
        #[serde(rename = "health stats")]
        health_stats: HealthStatsPeer,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct LNetStatistics {
        pub send_count: i64,
        pub recv_count: i64,
        pub drop_count: i64,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct LNetStatsStatistics {
        pub msgs_alloc: i64,
        pub msgs_max: i64,
        pub rst_alloc: i64,
        pub errors: i64,
        pub send_count: i64,
        pub resend_count: i64,
        pub response_timeout_count: i64,
        pub local_interrupt_count: i64,
        pub local_dropped_count: i64,
        pub local_aborted_count: i64,
        pub local_no_route_count: i64,
        pub local_timeout_count: i64,
        pub local_error_count: i64,
        pub remote_dropped_count: i64,
        pub remote_error_count: i64,
        pub remote_timeout_count: i64,
        pub network_timeout_count: i64,
        pub recv_count: i64,
        pub route_count: i64,
        pub drop_count: i64,
        pub send_length: i64,
        pub recv_length: i64,
        pub route_length: i64,
        pub drop_length: i64,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct Tunables {
        pub peer_timeout: i64,
        pub peer_credits: i64,
        pub peer_buffer_credits: i64,
        pub credits: i64,
    }
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct Stat {
    pub name: String,
    pub units: String,
    pub samples: u64,
    pub min: Option<u64>,
    pub max: Option<u64>,
    pub sum: Option<u64>,
    pub sumsquare: Option<u64>,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
/// A Stat specific to a host.
pub struct HostStat<T> {
    pub param: Param,
    pub value: T,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum TargetVariant {
    Ost,
    Mgt,
    Mdt,
}

impl fmt::Display for TargetVariant {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            TargetVariant::Ost => write!(f, "OST"),
            TargetVariant::Mgt => write!(f, "MGT"),
            TargetVariant::Mdt => write!(f, "MDT"),
        }
    }
}

impl Deref for TargetVariant {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        match *self {
            TargetVariant::Ost => "OST",
            TargetVariant::Mgt => "MGT",
            TargetVariant::Mdt => "MDT",
        }
    }
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
/// Stats specific to a target.
pub struct TargetStat<T> {
    pub kind: TargetVariant,
    pub param: Param,
    pub target: Target,
    pub value: T,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
/// Stats specific to a LNet Nid.
pub struct LNetStat<T> {
    pub nid: String,
    pub param: Param,
    pub value: T,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
/// Stats global across LNet Nids.
pub struct LNetStatGlobal<T> {
    pub param: Param,
    pub value: T,
}

impl TryFrom<&Target> for TargetVariant {
    type Error = LustreCollectorError;

    fn try_from(x: &Target) -> Result<Self, Self::Error> {
        let x = x.deref().to_lowercase();
        let x = x.trim();

        if x == "mgs" {
            return Ok(TargetVariant::Mgt);
        }

        let target_name = x.rsplit_once('-').map(|(_, x)| x);

        match target_name {
            Some(x) if x.starts_with("ost") => Ok(TargetVariant::Ost),
            Some(x) if x.starts_with("mdt") => Ok(TargetVariant::Mdt),
            None | Some(_) => Err(LustreCollectorError::ConversionError(format!(
                "Could not convert {x} to target variant"
            ))),
        }
    }
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct BrwStatsBucket {
    pub name: u64,
    pub read: u64,
    pub write: u64,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct BrwStats {
    pub name: String,
    pub unit: String,
    pub buckets: Vec<BrwStatsBucket>,
}

#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum RecoveryStatus {
    Complete,
    Inactive,
    Waiting,
    WaitingForClients,
    Recovering,
    Unknown,
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum HostStats {
    MemusedMax(HostStat<u64>),
    Memused(HostStat<u64>),
    LNetMemUsed(HostStat<u64>),
    HealthCheck(HostStat<String>),
}

/// A Stat specific to a node.
#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct NodeStat<T> {
    pub param: Param,
    pub value: T,
}
/// Top level node stats (not directly Lustre related)
#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum NodeStats {
    CpuUser(NodeStat<u64>),
    CpuSystem(NodeStat<u64>),
    CpuIowait(NodeStat<u64>),
    CpuTotal(NodeStat<u64>),
    MemTotal(NodeStat<u64>),
    MemFree(NodeStat<u64>),
    SwapTotal(NodeStat<u64>),
    SwapFree(NodeStat<u64>),
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct FsName(pub String);

/// The target stats currently collected
#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum TargetStats {
    /// Operations per OST. Read and write data is particularly interesting
    JobStatsOst(TargetStat<Option<Vec<JobStatOst>>>),
    Stats(TargetStat<Vec<Stat>>),
    BrwStats(TargetStat<Vec<BrwStats>>),
    JobStatsMdt(TargetStat<Option<Vec<JobStatMdt>>>),
    /// Available inodes
    FilesFree(TargetStat<u64>),
    /// Total inodes
    FilesTotal(TargetStat<u64>),
    /// Type of target
    FsType(TargetStat<String>),
    /// Available disk space
    BytesAvail(TargetStat<u64>),
    /// Free disk space
    BytesFree(TargetStat<u64>),
    /// Total disk space
    BytesTotal(TargetStat<u64>),
    NumExports(TargetStat<u64>),
    TotDirty(TargetStat<u64>),
    TotGranted(TargetStat<u64>),
    TotPending(TargetStat<u64>),
    ContendedLocks(TargetStat<u64>),
    ContentionSeconds(TargetStat<u64>),
    ConnectedClients(TargetStat<u64>),
    CtimeAgeLimit(TargetStat<u64>),
    EarlyLockCancel(TargetStat<u64>),
    FsNames(TargetStat<Vec<FsName>>),
    LockCount(TargetStat<u64>),
    LockTimeouts(TargetStat<u64>),
    LockUnusedCount(TargetStat<u64>),
    LruMaxAge(TargetStat<u64>),
    LruSize(TargetStat<u64>),
    MaxNolockBytes(TargetStat<u64>),
    MaxParallelAst(TargetStat<u64>),
    ResourceCount(TargetStat<u64>),
    ThreadsMin(TargetStat<u64>),
    ThreadsMax(TargetStat<u64>),
    ThreadsStarted(TargetStat<u64>),
    RecoveryStatus(TargetStat<RecoveryStatus>),
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum LNetStats {
    SendCount(LNetStat<i64>),
    RecvCount(LNetStat<i64>),
    DropCount(LNetStat<i64>),
    SendLength(LNetStatGlobal<i64>),
    RecvLength(LNetStatGlobal<i64>),
    DropLength(LNetStatGlobal<i64>),
}

#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum Record {
    Host(HostStats),
    LNetStat(LNetStats),
    Node(NodeStats),
    Target(TargetStats),
}
