//! Certificate service implementation

use std::sync::Arc;

use base64::Engine as _;
use crate::metadata::LbMetadataStore;
use fiberlb_api::{
    certificate_service_server::CertificateService,
    CreateCertificateRequest, CreateCertificateResponse,
    DeleteCertificateRequest, DeleteCertificateResponse,
    GetCertificateRequest, GetCertificateResponse,
    ListCertificatesRequest, ListCertificatesResponse,
    Certificate as ProtoCertificate, CertificateType as ProtoCertificateType,
};
use fiberlb_types::{
    Certificate, CertificateId, CertificateType, LoadBalancerId,
};
use iam_service_auth::{get_tenant_context, resource_for_tenant, AuthService};
use tonic::{Request, Response, Status};
use uuid::Uuid;

/// Certificate service implementation
pub struct CertificateServiceImpl {
    metadata: Arc<LbMetadataStore>,
    auth: Arc<AuthService>,
}

impl CertificateServiceImpl {
    /// Create a new CertificateServiceImpl
    pub fn new(metadata: Arc<LbMetadataStore>, auth: Arc<AuthService>) -> Self {
        Self { metadata, auth }
    }
}

const ACTION_CERTS_CREATE: &str = "network:certificates:create";
const ACTION_CERTS_READ: &str = "network:certificates:read";
const ACTION_CERTS_LIST: &str = "network:certificates:list";
const ACTION_CERTS_DELETE: &str = "network:certificates:delete";

/// Convert domain Certificate to proto
fn certificate_to_proto(cert: &Certificate) -> ProtoCertificate {
    ProtoCertificate {
        id: cert.id.to_string(),
        loadbalancer_id: cert.loadbalancer_id.to_string(),
        name: cert.name.clone(),
        certificate: cert.certificate.clone(),
        private_key: cert.private_key.clone(),
        cert_type: match cert.cert_type {
            CertificateType::Server => ProtoCertificateType::Server.into(),
            CertificateType::ClientCa => ProtoCertificateType::ClientCa.into(),
            CertificateType::Sni => ProtoCertificateType::Sni.into(),
        },
        expires_at: cert.expires_at,
        created_at: cert.created_at,
        updated_at: cert.updated_at,
    }
}

/// Parse CertificateId from string
fn parse_certificate_id(id: &str) -> Result<CertificateId, Status> {
    let uuid: Uuid = id
        .parse()
        .map_err(|_| Status::invalid_argument("invalid certificate ID"))?;
    Ok(CertificateId::from_uuid(uuid))
}

/// Parse LoadBalancerId from string
fn parse_lb_id(id: &str) -> Result<LoadBalancerId, Status> {
    let uuid: Uuid = id
        .parse()
        .map_err(|_| Status::invalid_argument("invalid load balancer ID"))?;
    Ok(LoadBalancerId::from_uuid(uuid))
}

/// Convert proto certificate type to domain
fn proto_to_cert_type(cert_type: i32) -> CertificateType {
    match ProtoCertificateType::try_from(cert_type) {
        Ok(ProtoCertificateType::Server) => CertificateType::Server,
        Ok(ProtoCertificateType::ClientCa) => CertificateType::ClientCa,
        Ok(ProtoCertificateType::Sni) => CertificateType::Sni,
        _ => CertificateType::Server,
    }
}

#[tonic::async_trait]
impl CertificateService for CertificateServiceImpl {
    async fn create_certificate(
        &self,
        request: Request<CreateCertificateRequest>,
    ) -> Result<Response<CreateCertificateResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();

        // Validate required fields
        if req.name.is_empty() {
            return Err(Status::invalid_argument("name is required"));
        }
        if req.loadbalancer_id.is_empty() {
            return Err(Status::invalid_argument("loadbalancer_id is required"));
        }
        if req.certificate.is_empty() {
            return Err(Status::invalid_argument("certificate is required"));
        }
        if req.private_key.is_empty() {
            return Err(Status::invalid_argument("private_key is required"));
        }

        let lb_id = parse_lb_id(&req.loadbalancer_id)?;

        // Verify load balancer exists
        let lb = self.metadata
            .load_lb_by_id(&lb_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("load balancer not found"))?;

        if lb.org_id != tenant.org_id || lb.project_id != tenant.project_id {
            return Err(Status::permission_denied("load balancer not in tenant scope"));
        }

        self.auth
            .authorize(
                &tenant,
                ACTION_CERTS_CREATE,
                &resource_for_tenant("certificate", "*", &lb.org_id, &lb.project_id),
            )
            .await?;

        // Parse certificate type
        let cert_type = proto_to_cert_type(req.cert_type);

        // TODO: Parse certificate to extract expiry date
        // For now, set expires_at to 1 year from now
        let expires_at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs() + (365 * 24 * 60 * 60);

        // Create new certificate
        let certificate = Certificate::new(
            &req.name,
            lb_id,
            &req.certificate,
            &req.private_key,
            cert_type,
            expires_at,
        );

        // Save certificate
        self.metadata
            .save_certificate(&certificate)
            .await
            .map_err(|e| Status::internal(format!("failed to save certificate: {}", e)))?;

        Ok(Response::new(CreateCertificateResponse {
            certificate: Some(certificate_to_proto(&certificate)),
        }))
    }

    async fn get_certificate(
        &self,
        request: Request<GetCertificateRequest>,
    ) -> Result<Response<GetCertificateResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();

        if req.id.is_empty() {
            return Err(Status::invalid_argument("id is required"));
        }

        let cert_id = parse_certificate_id(&req.id)?;

        let certificate = self
            .metadata
            .find_certificate_by_id(&cert_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("certificate not found"))?;

        let lb = self
            .metadata
            .load_lb_by_id(&certificate.loadbalancer_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("load balancer not found"))?;

        if lb.org_id != tenant.org_id || lb.project_id != tenant.project_id {
            return Err(Status::permission_denied("load balancer not in tenant scope"));
        }

        self.auth
            .authorize(
                &tenant,
                ACTION_CERTS_READ,
                &resource_for_tenant(
                    "certificate",
                    &certificate.id.to_string(),
                    &lb.org_id,
                    &lb.project_id,
                ),
            )
            .await?;

        Ok(Response::new(GetCertificateResponse {
            certificate: Some(certificate_to_proto(&certificate)),
        }))
    }

    async fn list_certificates(
        &self,
        request: Request<ListCertificatesRequest>,
    ) -> Result<Response<ListCertificatesResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();

        if req.loadbalancer_id.is_empty() {
            return Err(Status::invalid_argument("loadbalancer_id is required"));
        }

        let lb_id = parse_lb_id(&req.loadbalancer_id)?;

        let lb = self
            .metadata
            .load_lb_by_id(&lb_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("load balancer not found"))?;

        if lb.org_id != tenant.org_id || lb.project_id != tenant.project_id {
            return Err(Status::permission_denied("load balancer not in tenant scope"));
        }

        self.auth
            .authorize(
                &tenant,
                ACTION_CERTS_LIST,
                &resource_for_tenant("certificate", "*", &lb.org_id, &lb.project_id),
            )
            .await?;

        let certificates = self
            .metadata
            .list_certificates(&lb_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?;

        let page_size = if req.page_size == 0 {
            50
        } else {
            req.page_size as usize
        };

        let offset = if req.page_token.is_empty() {
            0
        } else {
            let decoded = base64::engine::general_purpose::STANDARD
                .decode(&req.page_token)
                .map_err(|_| Status::invalid_argument("invalid page_token"))?;
            let offset_str = String::from_utf8(decoded)
                .map_err(|_| Status::invalid_argument("invalid page_token encoding"))?;
            offset_str
                .parse::<usize>()
                .map_err(|_| Status::invalid_argument("invalid page_token format"))?
        };

        let total = certificates.len();
        let end = std::cmp::min(offset + page_size, total);
        let paginated = certificates.iter().skip(offset).take(page_size);

        let proto_certs: Vec<ProtoCertificate> = paginated.map(certificate_to_proto).collect();

        let next_page_token = if end < total {
            base64::engine::general_purpose::STANDARD.encode(end.to_string().as_bytes())
        } else {
            String::new()
        };

        Ok(Response::new(ListCertificatesResponse {
            certificates: proto_certs,
            next_page_token,
        }))
    }

    async fn delete_certificate(
        &self,
        request: Request<DeleteCertificateRequest>,
    ) -> Result<Response<DeleteCertificateResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();

        if req.id.is_empty() {
            return Err(Status::invalid_argument("id is required"));
        }

        let cert_id = parse_certificate_id(&req.id)?;

        // Load certificate to verify it exists
        let certificate = self
            .metadata
            .find_certificate_by_id(&cert_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("certificate not found"))?;

        let lb = self
            .metadata
            .load_lb_by_id(&certificate.loadbalancer_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("load balancer not found"))?;

        if lb.org_id != tenant.org_id || lb.project_id != tenant.project_id {
            return Err(Status::permission_denied("load balancer not in tenant scope"));
        }

        self.auth
            .authorize(
                &tenant,
                ACTION_CERTS_DELETE,
                &resource_for_tenant(
                    "certificate",
                    &certificate.id.to_string(),
                    &lb.org_id,
                    &lb.project_id,
                ),
            )
            .await?;

        // Delete certificate
        self.metadata
            .delete_certificate(&certificate)
            .await
            .map_err(|e| Status::internal(format!("failed to delete certificate: {}", e)))?;

        Ok(Response::new(DeleteCertificateResponse {}))
    }
}
