//! Node service implementation
//!
//! Handles node registration, heartbeat, and listing operations.

use crate::auth::{
    get_tenant_context, resolve_tenant_ids_from_context, resource_for_tenant, AuthService,
};
use crate::storage::Storage;
use chrono::Utc;
use k8shost_proto::{
    node_service_server::NodeService, HeartbeatRequest, HeartbeatResponse, ListNodesRequest,
    ListNodesResponse, RegisterNodeRequest, RegisterNodeResponse,
};
use std::sync::Arc;
use tonic::{Request, Response, Status};
use uuid::Uuid;

const ACTION_NODE_CREATE: &str = "k8s:nodes:create";
const ACTION_NODE_READ: &str = "k8s:nodes:read";
const ACTION_NODE_LIST: &str = "k8s:nodes:list";
const ACTION_NODE_UPDATE: &str = "k8s:nodes:update";

/// Node service implementation with storage backend
#[derive(Clone)]
pub struct NodeServiceImpl {
    storage: Arc<Storage>,
    auth: Arc<AuthService>,
}

impl NodeServiceImpl {
    pub fn new(storage: Arc<Storage>, auth: Arc<AuthService>) -> Self {
        Self { storage, auth }
    }

    /// Convert k8shost_types::Node to proto Node
    pub fn to_proto_node(node: &k8shost_types::Node) -> k8shost_proto::Node {
        let metadata = Some(k8shost_proto::ObjectMeta {
            name: node.metadata.name.clone(),
            namespace: node.metadata.namespace.clone(),
            uid: node.metadata.uid.clone(),
            resource_version: node.metadata.resource_version.clone(),
            creation_timestamp: node.metadata.creation_timestamp.map(|ts| ts.to_rfc3339()),
            labels: node.metadata.labels.clone(),
            annotations: node.metadata.annotations.clone(),
            org_id: node.metadata.org_id.clone(),
            project_id: node.metadata.project_id.clone(),
        });

        let spec = Some(k8shost_proto::NodeSpec {
            pod_cidr: node.spec.pod_cidr.clone(),
        });

        let status = node.status.as_ref().map(|s| k8shost_proto::NodeStatus {
            addresses: s
                .addresses
                .iter()
                .map(|a| k8shost_proto::NodeAddress {
                    r#type: a.r#type.clone(),
                    address: a.address.clone(),
                })
                .collect(),
            conditions: s
                .conditions
                .iter()
                .map(|c| k8shost_proto::NodeCondition {
                    r#type: c.r#type.clone(),
                    status: c.status.clone(),
                    reason: c.reason.clone(),
                    message: c.message.clone(),
                })
                .collect(),
            capacity: s.capacity.clone(),
            allocatable: s.allocatable.clone(),
        });

        k8shost_proto::Node {
            metadata,
            spec,
            status,
        }
    }

    /// Convert proto Node to k8shost_types::Node
    pub fn from_proto_node(proto: &k8shost_proto::Node) -> Result<k8shost_types::Node, Status> {
        let metadata = proto
            .metadata
            .as_ref()
            .ok_or_else(|| Status::invalid_argument("metadata is required"))?;

        let spec = proto
            .spec
            .as_ref()
            .ok_or_else(|| Status::invalid_argument("spec is required"))?;

        let meta = k8shost_types::ObjectMeta {
            name: metadata.name.clone(),
            namespace: metadata.namespace.clone(),
            uid: metadata.uid.clone(),
            resource_version: metadata.resource_version.clone(),
            creation_timestamp: metadata
                .creation_timestamp
                .as_ref()
                .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
                .map(|dt| dt.with_timezone(&Utc)),
            labels: metadata.labels.clone(),
            annotations: metadata.annotations.clone(),
            org_id: metadata.org_id.clone(),
            project_id: metadata.project_id.clone(),
        };

        let node_spec = k8shost_types::NodeSpec {
            pod_cidr: spec.pod_cidr.clone(),
        };

        let status = proto.status.as_ref().map(|s| k8shost_types::NodeStatus {
            addresses: s
                .addresses
                .iter()
                .map(|a| k8shost_types::NodeAddress {
                    r#type: a.r#type.clone(),
                    address: a.address.clone(),
                })
                .collect(),
            conditions: s
                .conditions
                .iter()
                .map(|c| k8shost_types::NodeCondition {
                    r#type: c.r#type.clone(),
                    status: c.status.clone(),
                    reason: c.reason.clone(),
                    message: c.message.clone(),
                })
                .collect(),
            capacity: s.capacity.clone(),
            allocatable: s.allocatable.clone(),
        });

        Ok(k8shost_types::Node {
            metadata: meta,
            spec: node_spec,
            status,
        })
    }

    /// Convert proto NodeStatus to k8shost_types::NodeStatus
    fn from_proto_node_status(proto: &k8shost_proto::NodeStatus) -> k8shost_types::NodeStatus {
        k8shost_types::NodeStatus {
            addresses: proto
                .addresses
                .iter()
                .map(|a| k8shost_types::NodeAddress {
                    r#type: a.r#type.clone(),
                    address: a.address.clone(),
                })
                .collect(),
            conditions: proto
                .conditions
                .iter()
                .map(|c| k8shost_types::NodeCondition {
                    r#type: c.r#type.clone(),
                    status: c.status.clone(),
                    reason: c.reason.clone(),
                    message: c.message.clone(),
                })
                .collect(),
            capacity: proto.capacity.clone(),
            allocatable: proto.allocatable.clone(),
        }
    }
}

#[tonic::async_trait]
impl NodeService for NodeServiceImpl {
    async fn register_node(
        &self,
        request: Request<RegisterNodeRequest>,
    ) -> Result<Response<RegisterNodeResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();
        let proto_node = req
            .node
            .ok_or_else(|| Status::invalid_argument("node is required"))?;

        let mut node = Self::from_proto_node(&proto_node)?;

        let (org_id, project_id) = resolve_tenant_ids_from_context(
            &tenant,
            node.metadata.org_id.as_deref().unwrap_or(""),
            node.metadata.project_id.as_deref().unwrap_or(""),
        )?;
        node.metadata.org_id = Some(org_id.clone());
        node.metadata.project_id = Some(project_id.clone());
        self.auth
            .authorize(
                &tenant,
                ACTION_NODE_CREATE,
                &resource_for_tenant("node", node.metadata.name.clone(), &org_id, &project_id),
            )
            .await?;

        // Assign UID if not present
        if node.metadata.uid.is_none() {
            node.metadata.uid = Some(Uuid::new_v4().to_string());
        }

        // Set creation timestamp
        if node.metadata.creation_timestamp.is_none() {
            node.metadata.creation_timestamp = Some(Utc::now());
        }

        // Set initial resource version
        if node.metadata.resource_version.is_none() {
            node.metadata.resource_version = Some("1".to_string());
        }

        // Store the node
        self.storage.put_node(&node).await?;

        let proto_node = Self::to_proto_node(&node);
        Ok(Response::new(RegisterNodeResponse {
            node: Some(proto_node),
        }))
    }

    async fn heartbeat(
        &self,
        request: Request<HeartbeatRequest>,
    ) -> Result<Response<HeartbeatResponse>, Status> {
        // Extract tenant context from authenticated request
        let tenant_context = get_tenant_context(&request)?;
        let req = request.into_inner();
        self.auth
            .authorize(
                &tenant_context,
                ACTION_NODE_UPDATE,
                &resource_for_tenant(
                    "node",
                    req.node_name.clone(),
                    &tenant_context.org_id,
                    &tenant_context.project_id,
                ),
            )
            .await?;

        // Get existing node
        let mut node = self
            .storage
            .get_node(
                &tenant_context.org_id,
                &tenant_context.project_id,
                &req.node_name,
            )
            .await?
            .ok_or_else(|| Status::not_found(format!("Node {} not found", req.node_name)))?;

        // Update status with heartbeat information
        if let Some(proto_status) = req.status {
            node.status = Some(Self::from_proto_node_status(&proto_status));
        }

        // Update last heartbeat timestamp in annotations
        node.metadata.annotations.insert(
            "k8shost.io/last-heartbeat".to_string(),
            Utc::now().to_rfc3339(),
        );

        // Increment resource version
        let current_version = node
            .metadata
            .resource_version
            .as_ref()
            .and_then(|v| v.parse::<u64>().ok())
            .unwrap_or(0);
        node.metadata.resource_version = Some((current_version + 1).to_string());

        // Store updated node
        self.storage.put_node(&node).await?;

        Ok(Response::new(HeartbeatResponse { success: true }))
    }

    async fn list_nodes(
        &self,
        request: Request<ListNodesRequest>,
    ) -> Result<Response<ListNodesResponse>, Status> {
        // Extract tenant context from authenticated request
        let tenant_context = get_tenant_context(&request)?;
        self.auth
            .authorize(
                &tenant_context,
                ACTION_NODE_LIST,
                &resource_for_tenant(
                    "node",
                    "*",
                    &tenant_context.org_id,
                    &tenant_context.project_id,
                ),
            )
            .await?;
        let _req = request.into_inner();

        let nodes = self
            .storage
            .list_nodes(&tenant_context.org_id, &tenant_context.project_id)
            .await?;

        let items: Vec<k8shost_proto::Node> = nodes.iter().map(Self::to_proto_node).collect();

        Ok(Response::new(ListNodesResponse { items }))
    }
}
