// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

use std::time::Duration;

use mssf_com::{
    FabricClient::{IFabricHealthClient4, IFabricNodeHealthResult},
    FabricTypes::{
        FABRIC_APPLICATION_HEALTH_REPORT, FABRIC_CLUSTER_HEALTH_QUERY_DESCRIPTION,
        FABRIC_CLUSTER_HEALTH_REPORT, FABRIC_DEPLOYED_APPLICATION_HEALTH_REPORT,
        FABRIC_DEPLOYED_SERVICE_PACKAGE_HEALTH_REPORT, FABRIC_HEALTH_INFORMATION,
        FABRIC_HEALTH_REPORT, FABRIC_HEALTH_REPORT_KIND_APPLICATION,
        FABRIC_HEALTH_REPORT_KIND_CLUSTER, FABRIC_HEALTH_REPORT_KIND_DEPLOYED_APPLICATION,
        FABRIC_HEALTH_REPORT_KIND_DEPLOYED_SERVICE_PACKAGE, FABRIC_HEALTH_REPORT_KIND_INVALID,
        FABRIC_HEALTH_REPORT_KIND_NODE, FABRIC_HEALTH_REPORT_KIND_PARTITION,
        FABRIC_HEALTH_REPORT_KIND_SERVICE, FABRIC_HEALTH_REPORT_KIND_STATEFUL_SERVICE_REPLICA,
        FABRIC_HEALTH_REPORT_KIND_STATELESS_SERVICE_INSTANCE, FABRIC_NODE_HEALTH_QUERY_DESCRIPTION,
        FABRIC_NODE_HEALTH_REPORT, FABRIC_PARTITION_HEALTH_REPORT, FABRIC_SERVICE_HEALTH_REPORT,
        FABRIC_STATEFUL_SERVICE_REPLICA_HEALTH_REPORT,
        FABRIC_STATELESS_SERVICE_INSTANCE_HEALTH_REPORT, FABRIC_URI,
    },
};

use crate::{
    mem::{BoxPool, GetRawWithBoxPool},
    runtime::executor::BoxedCancelToken,
    sync::{FabricReceiver, fabric_begin_end_proxy},
    types::{ClusterHealth, HealthReport, NodeHealthQueryDescription, NodeHealthResult},
};

/// Provides functionality to perform health related operations, like report and query health.
/// See C# API [here](https://docs.microsoft.com/en-us/dotnet/api/system.fabric.fabricclient.healthclient?view=azure-dotnet).
///
/// TODO: Implement full functionality of the HealthClient.
#[derive(Debug, Clone)]
pub struct HealthClient {
    com: IFabricHealthClient4,
}

impl From<IFabricHealthClient4> for HealthClient {
    fn from(value: IFabricHealthClient4) -> Self {
        Self { com: value }
    }
}

impl From<HealthClient> for IFabricHealthClient4 {
    fn from(value: HealthClient) -> Self {
        value.com
    }
}

// Public implementation block
impl HealthClient {
    /// Reports health on a Service Fabric entity. See C# API [here](https://docs.microsoft.com/en-us/dotnet/api/system.fabric.fabricclient.healthclient.reporthealth?view=azure-dotnet).
    ///
    /// Remarks:
    /// When a cluster is secured, the health client needs administrator permission to be able to send the reports.
    /// Read more about [connecting to a cluster using the FabricClient APIs](https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-connect-to-secure-cluster).
    /// For more information about health reporting, see [Service Fabric health monitoring](https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-health-introduction).
    pub fn report_health(&self, health_report: &HealthReport) -> crate::Result<()> {
        match health_report {
            HealthReport::Invalid => {
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_INVALID,
                    Value: std::ptr::null_mut(),
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::StatefulServiceReplica(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_STATEFUL_SERVICE_REPLICA_HEALTH_REPORT {
                    PartitionId: health_report.partition_id,
                    ReplicaId: health_report.replica_id,
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_STATEFUL_SERVICE_REPLICA,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::StatelessServiceInstance(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_STATELESS_SERVICE_INSTANCE_HEALTH_REPORT {
                    PartitionId: health_report.partition_id,
                    InstanceId: health_report.instance_id,
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_STATELESS_SERVICE_INSTANCE,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::Partition(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_PARTITION_HEALTH_REPORT {
                    PartitionId: health_report.partition_id,
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_PARTITION,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::Node(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_NODE_HEALTH_REPORT {
                    NodeName: health_report.node_name.as_pcwstr(),
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_NODE,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::Service(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_SERVICE_HEALTH_REPORT {
                    ServiceName: FABRIC_URI(health_report.service_name.as_ptr() as *mut u16),
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_SERVICE,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::Application(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_APPLICATION_HEALTH_REPORT {
                    ApplicationName: FABRIC_URI(health_report.application_name.as_ptr() as *mut u16),
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_APPLICATION,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::DeployedApplication(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_DEPLOYED_APPLICATION_HEALTH_REPORT {
                    ApplicationName: FABRIC_URI(health_report.application_name.as_ptr() as *mut u16),
                    NodeName: health_report.node_name.as_pcwstr(),
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_DEPLOYED_APPLICATION,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::DeployedServicePackage(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_DEPLOYED_SERVICE_PACKAGE_HEALTH_REPORT {
                    ApplicationName: FABRIC_URI(health_report.application_name.as_ptr() as *mut u16),
                    ServiceManifestName: health_report.service_manifest_name.as_pcwstr(),
                    NodeName: health_report.node_name.as_pcwstr(),
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_DEPLOYED_SERVICE_PACKAGE,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
            HealthReport::Cluster(health_report) => {
                let fabric_health_info =
                    FABRIC_HEALTH_INFORMATION::from(&health_report.health_information);
                let fabric_health_report_value = FABRIC_CLUSTER_HEALTH_REPORT {
                    HealthInformation: &fabric_health_info,
                    Reserved: std::ptr::null_mut(),
                };
                let fabric_health_report = FABRIC_HEALTH_REPORT {
                    Kind: FABRIC_HEALTH_REPORT_KIND_CLUSTER,
                    Value: &fabric_health_report_value as *const _ as *mut _,
                };
                unsafe { self.com.ReportHealth(&fabric_health_report) }
            }
        }.map_err(crate::Error::from)
    }
}

impl HealthClient {
    fn get_node_health_internal(
        &self,
        desc: &FABRIC_NODE_HEALTH_QUERY_DESCRIPTION,
        timeout_milliseconds: u32,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> FabricReceiver<crate::Result<IFabricNodeHealthResult>> {
        let com1 = &self.com;
        let com2 = self.com.clone();
        fabric_begin_end_proxy(
            move |callback| unsafe {
                com1.BeginGetNodeHealth2(desc, timeout_milliseconds, callback)
            },
            move |ctx| unsafe { com2.EndGetNodeHealth2(ctx) },
            cancellation_token,
        )
    }

    fn get_cluster_health_internal(
        &self,
        desc: &FABRIC_CLUSTER_HEALTH_QUERY_DESCRIPTION,
        timeout_milliseconds: u32,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> FabricReceiver<crate::Result<mssf_com::FabricClient::IFabricClusterHealthResult>> {
        let com1 = &self.com;
        let com2 = self.com.clone();
        fabric_begin_end_proxy(
            move |callback| unsafe {
                com1.BeginGetClusterHealth2(desc, timeout_milliseconds, callback)
            },
            move |ctx| unsafe { com2.EndGetClusterHealth2(ctx) },
            cancellation_token,
        )
    }

    fn get_application_health_internal(
        &self,
        desc: &mssf_com::FabricTypes::FABRIC_APPLICATION_HEALTH_QUERY_DESCRIPTION,
        timeout_milliseconds: u32,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> FabricReceiver<crate::Result<mssf_com::FabricClient::IFabricApplicationHealthResult>> {
        let com1 = &self.com;
        let com2 = self.com.clone();
        fabric_begin_end_proxy(
            move |callback| unsafe {
                com1.BeginGetApplicationHealth2(desc, timeout_milliseconds, callback)
            },
            move |ctx| unsafe { com2.EndGetApplicationHealth2(ctx) },
            cancellation_token,
        )
    }
    fn get_partition_health_internal(
        &self,
        desc: &mssf_com::FabricTypes::FABRIC_PARTITION_HEALTH_QUERY_DESCRIPTION,
        timeout_milliseconds: u32,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> FabricReceiver<crate::Result<mssf_com::FabricClient::IFabricPartitionHealthResult>> {
        let com1 = &self.com;
        let com2 = self.com.clone();
        fabric_begin_end_proxy(
            move |callback| unsafe {
                com1.BeginGetPartitionHealth2(desc, timeout_milliseconds, callback)
            },
            move |ctx| unsafe { com2.EndGetPartitionHealth2(ctx) },
            cancellation_token,
        )
    }
    fn get_service_health_internal(
        &self,
        desc: &mssf_com::FabricTypes::FABRIC_SERVICE_HEALTH_QUERY_DESCRIPTION,
        timeout_milliseconds: u32,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> FabricReceiver<crate::Result<mssf_com::FabricClient::IFabricServiceHealthResult>> {
        let com1 = &self.com;
        let com2 = self.com.clone();
        fabric_begin_end_proxy(
            move |callback| unsafe {
                com1.BeginGetServiceHealth2(desc, timeout_milliseconds, callback)
            },
            move |ctx| unsafe { com2.EndGetServiceHealth2(ctx) },
            cancellation_token,
        )
    }
    fn get_replica_health_internal(
        &self,
        desc: &mssf_com::FabricTypes::FABRIC_REPLICA_HEALTH_QUERY_DESCRIPTION,
        timeout_milliseconds: u32,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> FabricReceiver<crate::Result<mssf_com::FabricClient::IFabricReplicaHealthResult>> {
        let com1 = &self.com;
        let com2 = self.com.clone();
        fabric_begin_end_proxy(
            move |callback| unsafe {
                com1.BeginGetReplicaHealth2(desc, timeout_milliseconds, callback)
            },
            move |ctx| unsafe { com2.EndGetReplicaHealth2(ctx) },
            cancellation_token,
        )
    }
}

impl HealthClient {
    /// Gets the health of a node.
    ///
    /// See C# API [here](https://learn.microsoft.com/en-us/dotnet/api/system.fabric.fabricclient.healthclient.getnodehealthasync?view=azure-dotnet).
    pub async fn get_node_health(
        &self,
        desc: &NodeHealthQueryDescription,
        timeout: Duration,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> crate::Result<NodeHealthResult> {
        let com = {
            let mut pool = BoxPool::new();
            let desc_raw = desc.get_raw_with_pool(&mut pool);
            self.get_node_health_internal(&desc_raw, timeout.as_millis() as u32, cancellation_token)
        }
        .await??;
        Ok(NodeHealthResult::from_com(&com))
    }

    /// Gets the health of the cluster.
    pub async fn get_cluster_health(
        &self,
        desc: &crate::types::ClusterHealthQueryDescription,
        timeout: Duration,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> crate::Result<ClusterHealth> {
        let com = {
            // Build the app policy map
            let mut pool = BoxPool::new();
            let desc_raw = desc.get_raw_with_pool(&mut pool);
            self.get_cluster_health_internal(
                &desc_raw,
                timeout.as_millis() as u32,
                cancellation_token,
            )
        }
        .await??;
        Ok(ClusterHealth::from(&com))
    }

    /// Gets the health of an application.
    pub async fn get_application_health(
        &self,
        desc: &crate::types::ApplicationHealthQueryDescription,
        timeout: Duration,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> crate::Result<crate::types::ApplicationHealth> {
        let com = {
            let mut pool = BoxPool::new();
            let desc_raw = desc.get_raw_with_pool(&mut pool);
            self.get_application_health_internal(
                &desc_raw,
                timeout.as_millis() as u32,
                cancellation_token,
            )
        }
        .await??;
        Ok(crate::types::ApplicationHealth::from(&com))
    }

    pub async fn get_partition_health(
        &self,
        desc: &crate::types::PartitionHealthQueryDescription,
        timeout: Duration,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> crate::Result<crate::types::PartitionHealthResult> {
        let com = {
            let mut pool = BoxPool::new();
            let desc_raw = desc.get_raw_with_pool(&mut pool);
            self.get_partition_health_internal(
                &desc_raw,
                timeout.as_millis() as u32,
                cancellation_token,
            )
        }
        .await??;
        Ok(crate::types::PartitionHealthResult::from(&com))
    }

    /// Gets the health of a service.
    pub async fn get_service_health(
        &self,
        desc: &crate::types::ServiceHealthQueryDescription,
        timeout: Duration,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> crate::Result<crate::types::ServiceHealthResult> {
        let com = {
            let mut pool = BoxPool::new();
            let desc_raw = desc.get_raw_with_pool(&mut pool);
            self.get_service_health_internal(
                &desc_raw,
                timeout.as_millis() as u32,
                cancellation_token,
            )
        }
        .await??;
        Ok(crate::types::ServiceHealthResult::from(&com))
    }

    /// Gets the health of a replica.
    pub async fn get_replica_health(
        &self,
        desc: &crate::types::ReplicaHealthQueryDescription,
        timeout: Duration,
        cancellation_token: Option<BoxedCancelToken>,
    ) -> crate::Result<crate::types::ReplicaHealthResult> {
        let com = {
            let mut pool = BoxPool::new();
            let desc_raw = desc.get_raw_with_pool(&mut pool);
            self.get_replica_health_internal(
                &desc_raw,
                timeout.as_millis() as u32,
                cancellation_token,
            )
        }
        .await??;
        Ok(crate::types::ReplicaHealthResult::from(&com))
    }
}
