//! Service service implementation
//!
//! Handles CRUD operations for Kubernetes Services with cluster IP allocation.

use crate::auth::{
    get_tenant_context, resolve_tenant_ids_from_context, resource_for_tenant, AuthService,
};
use crate::ipam_client::IpamClient;
use crate::storage::Storage;
use chrono::Utc;
use k8shost_proto::{
    service_service_server::ServiceService, CreateServiceRequest, CreateServiceResponse,
    DeleteServiceRequest, DeleteServiceResponse, GetServiceRequest, GetServiceResponse,
    ListServicesRequest, ListServicesResponse, UpdateServiceRequest, UpdateServiceResponse,
};
use std::sync::Arc;
use tonic::{Request, Response, Status};
use tracing::warn;
use uuid::Uuid;

const ACTION_SERVICE_CREATE: &str = "k8s:services:create";
const ACTION_SERVICE_READ: &str = "k8s:services:read";
const ACTION_SERVICE_LIST: &str = "k8s:services:list";
const ACTION_SERVICE_UPDATE: &str = "k8s:services:update";
const ACTION_SERVICE_DELETE: &str = "k8s:services:delete";

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

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

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

        let spec = Some(k8shost_proto::ServiceSpec {
            ports: svc
                .spec
                .ports
                .iter()
                .map(|p| k8shost_proto::ServicePort {
                    name: p.name.clone(),
                    port: p.port,
                    target_port: p.target_port,
                    protocol: p.protocol.clone(),
                })
                .collect(),
            selector: svc.spec.selector.clone(),
            cluster_ip: svc.spec.cluster_ip.clone(),
            r#type: svc.spec.r#type.clone(),
        });

        let status = svc.status.as_ref().map(|s| k8shost_proto::ServiceStatus {
            load_balancer: s
                .load_balancer
                .as_ref()
                .map(|lb| k8shost_proto::LoadBalancerStatus {
                    ingress: lb
                        .ingress
                        .iter()
                        .map(|ing| k8shost_proto::LoadBalancerIngress {
                            ip: ing.ip.clone(),
                            hostname: ing.hostname.clone(),
                        })
                        .collect(),
                }),
        });

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

    /// Convert proto Service to k8shost_types::Service
    pub fn from_proto_service(
        proto: &k8shost_proto::Service,
    ) -> Result<k8shost_types::Service, 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 svc_spec = k8shost_types::ServiceSpec {
            ports: spec
                .ports
                .iter()
                .map(|p| k8shost_types::ServicePort {
                    name: p.name.clone(),
                    port: p.port,
                    target_port: p.target_port,
                    protocol: p.protocol.clone(),
                })
                .collect(),
            selector: spec.selector.clone(),
            cluster_ip: spec.cluster_ip.clone(),
            r#type: spec.r#type.clone(),
        };

        let status = proto.status.as_ref().map(|s| k8shost_types::ServiceStatus {
            load_balancer: s
                .load_balancer
                .as_ref()
                .map(|lb| k8shost_types::LoadBalancerStatus {
                    ingress: lb
                        .ingress
                        .iter()
                        .map(|ing| k8shost_types::LoadBalancerIngress {
                            ip: ing.ip.clone(),
                            hostname: ing.hostname.clone(),
                        })
                        .collect(),
                }),
        });

        Ok(k8shost_types::Service {
            metadata: meta,
            spec: svc_spec,
            status,
        })
    }
}

fn forwarded_authorization<T>(request: &Request<T>) -> Option<String> {
    request
        .metadata()
        .get("authorization")
        .and_then(|value| value.to_str().ok())
        .map(str::to_owned)
}

#[tonic::async_trait]
impl ServiceService for ServiceServiceImpl {
    async fn create_service(
        &self,
        request: Request<CreateServiceRequest>,
    ) -> Result<Response<CreateServiceResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let authorization = forwarded_authorization(&request);
        let req = request.into_inner();
        let proto_service = req
            .service
            .ok_or_else(|| Status::invalid_argument("service is required"))?;

        let mut service = Self::from_proto_service(&proto_service)?;

        let (org_id, project_id) = resolve_tenant_ids_from_context(
            &tenant,
            service.metadata.org_id.as_deref().unwrap_or(""),
            service.metadata.project_id.as_deref().unwrap_or(""),
        )?;
        service.metadata.org_id = Some(org_id.clone());
        service.metadata.project_id = Some(project_id.clone());
        if service.metadata.namespace.is_none() {
            return Err(Status::invalid_argument(
                "namespace is required in metadata",
            ));
        }
        self.auth
            .authorize(
                &tenant,
                ACTION_SERVICE_CREATE,
                &resource_for_tenant("service", "*", &org_id, &project_id),
            )
            .await?;

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

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

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

        // Allocate cluster IP if not present and service type is ClusterIP
        if service.spec.cluster_ip.is_none() {
            let svc_type = service.spec.r#type.as_deref().unwrap_or("ClusterIP");
            if svc_type == "ClusterIP" || svc_type == "LoadBalancer" {
                // Get org_id, project_id, and uid for IPAM
                let org_id = service.metadata.org_id.as_ref().unwrap();
                let project_id = service.metadata.project_id.as_ref().unwrap();
                let service_uid = service.metadata.uid.as_ref().unwrap();

                // Allocate IP from IPAM
                let cluster_ip = self
                    .ipam_client
                    .allocate_cluster_ip(org_id, project_id, service_uid, authorization.as_deref())
                    .await
                    .map_err(|e| {
                        Status::internal(format!("Failed to allocate Cluster IP: {}", e))
                    })?;
                service.spec.cluster_ip = Some(cluster_ip);
            }
        }

        // Store the service
        self.storage.put_service(&service).await?;

        let proto_service = Self::to_proto_service(&service);
        Ok(Response::new(CreateServiceResponse {
            service: Some(proto_service),
        }))
    }

    async fn get_service(
        &self,
        request: Request<GetServiceRequest>,
    ) -> Result<Response<GetServiceResponse>, Status> {
        // Extract tenant context from authenticated request
        let tenant_context = get_tenant_context(&request)?;
        let req = request.into_inner();
        let service_key = format!("{}/{}", req.namespace, req.name);
        self.auth
            .authorize(
                &tenant_context,
                ACTION_SERVICE_READ,
                &resource_for_tenant(
                    "service",
                    service_key,
                    &tenant_context.org_id,
                    &tenant_context.project_id,
                ),
            )
            .await?;

        let service = self
            .storage
            .get_service(
                &tenant_context.org_id,
                &tenant_context.project_id,
                &req.namespace,
                &req.name,
            )
            .await?;

        if let Some(service) = service {
            let proto_service = Self::to_proto_service(&service);
            Ok(Response::new(GetServiceResponse {
                service: Some(proto_service),
            }))
        } else {
            Err(Status::not_found(format!("Service {} not found", req.name)))
        }
    }

    async fn list_services(
        &self,
        request: Request<ListServicesRequest>,
    ) -> Result<Response<ListServicesResponse>, Status> {
        // Extract tenant context from authenticated request
        let tenant_context = get_tenant_context(&request)?;
        self.auth
            .authorize(
                &tenant_context,
                ACTION_SERVICE_LIST,
                &resource_for_tenant(
                    "service",
                    "*",
                    &tenant_context.org_id,
                    &tenant_context.project_id,
                ),
            )
            .await?;
        let req = request.into_inner();

        let namespace = req.namespace.as_deref();

        let services = self
            .storage
            .list_services(
                &tenant_context.org_id,
                &tenant_context.project_id,
                namespace,
            )
            .await?;

        let items: Vec<k8shost_proto::Service> =
            services.iter().map(Self::to_proto_service).collect();

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

    async fn update_service(
        &self,
        request: Request<UpdateServiceRequest>,
    ) -> Result<Response<UpdateServiceResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();
        let proto_service = req
            .service
            .ok_or_else(|| Status::invalid_argument("service is required"))?;

        let mut service = Self::from_proto_service(&proto_service)?;

        let (org_id, project_id) = resolve_tenant_ids_from_context(
            &tenant,
            service.metadata.org_id.as_deref().unwrap_or(""),
            service.metadata.project_id.as_deref().unwrap_or(""),
        )?;
        service.metadata.org_id = Some(org_id.clone());
        service.metadata.project_id = Some(project_id.clone());
        if service.metadata.namespace.is_none() {
            return Err(Status::invalid_argument("namespace is required"));
        }
        let service_key = format!(
            "{}/{}",
            service.metadata.namespace.as_deref().unwrap_or("default"),
            service.metadata.name
        );
        self.auth
            .authorize(
                &tenant,
                ACTION_SERVICE_UPDATE,
                &resource_for_tenant("service", service_key, &org_id, &project_id),
            )
            .await?;

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

        // Update the service
        self.storage.put_service(&service).await?;

        let proto_service = Self::to_proto_service(&service);
        Ok(Response::new(UpdateServiceResponse {
            service: Some(proto_service),
        }))
    }

    async fn delete_service(
        &self,
        request: Request<DeleteServiceRequest>,
    ) -> Result<Response<DeleteServiceResponse>, Status> {
        // Extract tenant context from authenticated request
        let tenant_context = get_tenant_context(&request)?;
        let authorization = forwarded_authorization(&request);
        let req = request.into_inner();
        let service_key = format!("{}/{}", req.namespace, req.name);
        self.auth
            .authorize(
                &tenant_context,
                ACTION_SERVICE_DELETE,
                &resource_for_tenant(
                    "service",
                    service_key,
                    &tenant_context.org_id,
                    &tenant_context.project_id,
                ),
            )
            .await?;

        // Get the service before deleting to release its IP
        if let Ok(Some(service)) = self
            .storage
            .get_service(
                &tenant_context.org_id,
                &tenant_context.project_id,
                &req.namespace,
                &req.name,
            )
            .await
        {
            // Release cluster IP if allocated
            if let Some(cluster_ip) = &service.spec.cluster_ip {
                if let Err(e) = self
                    .ipam_client
                    .release_cluster_ip(
                        &tenant_context.org_id,
                        &tenant_context.project_id,
                        cluster_ip,
                        authorization.as_deref(),
                    )
                    .await
                {
                    warn!("Failed to release Cluster IP {}: {}", cluster_ip, e);
                    // Continue with deletion even if IP release fails
                }
            }
        }

        let existed = self
            .storage
            .delete_service(
                &tenant_context.org_id,
                &tenant_context.project_id,
                &req.namespace,
                &req.name,
            )
            .await?;

        Ok(Response::new(DeleteServiceResponse { success: existed }))
    }
}
