//! Listener service implementation

use std::sync::Arc;

use crate::{DataPlane, L7DataPlane};
use base64::Engine as _;
use crate::metadata::LbMetadataStore;
use fiberlb_api::{
    listener_service_server::ListenerService,
    CreateListenerRequest, CreateListenerResponse,
    DeleteListenerRequest, DeleteListenerResponse,
    GetListenerRequest, GetListenerResponse,
    ListListenersRequest, ListListenersResponse,
    UpdateListenerRequest, UpdateListenerResponse,
    Listener as ProtoListener, ListenerProtocol as ProtoListenerProtocol,
    TlsConfig as ProtoTlsConfig, TlsVersion as ProtoTlsVersion,
};
use fiberlb_types::{
    Listener, ListenerId, ListenerProtocol, LoadBalancerId, PoolId, TlsConfig, TlsVersion,
};
use iam_service_auth::{get_tenant_context, resource_for_tenant, AuthService};
use tonic::{Request, Response, Status};
use uuid::Uuid;

/// Listener service implementation
pub struct ListenerServiceImpl {
    metadata: Arc<LbMetadataStore>,
    auth: Arc<AuthService>,
    dataplane: Arc<DataPlane>,
    l7_dataplane: Arc<L7DataPlane>,
}

impl ListenerServiceImpl {
    /// Create a new ListenerServiceImpl
    pub fn new(
        metadata: Arc<LbMetadataStore>,
        auth: Arc<AuthService>,
        dataplane: Arc<DataPlane>,
        l7_dataplane: Arc<L7DataPlane>,
    ) -> Self {
        Self { metadata, auth, dataplane, l7_dataplane }
    }

    async fn start_runtime_listener(&self, listener: &Listener) -> Result<(), Status> {
        if !listener.enabled {
            return Ok(());
        }

        if listener.is_l7() {
            self.l7_dataplane
                .start_listener(listener.id)
                .await
                .map_err(|e| Status::internal(format!("failed to start L7 listener runtime: {}", e)))
        } else {
            self.dataplane
                .start_listener(listener.id)
                .await
                .map_err(|e| Status::internal(format!("failed to start listener runtime: {}", e)))
        }
    }

    async fn stop_runtime_listener(&self, listener: &Listener) {
        if listener.is_l7() {
            if let Err(e) = self.l7_dataplane.stop_listener(&listener.id).await {
                tracing::debug!("failed to stop L7 listener {}: {}", listener.id, e);
            }
        } else if let Err(e) = self.dataplane.stop_listener(&listener.id).await {
            tracing::debug!("failed to stop listener {}: {}", listener.id, e);
        }
    }
}

const ACTION_LISTENERS_CREATE: &str = "network:listeners:create";
const ACTION_LISTENERS_READ: &str = "network:listeners:read";
const ACTION_LISTENERS_LIST: &str = "network:listeners:list";
const ACTION_LISTENERS_UPDATE: &str = "network:listeners:update";
const ACTION_LISTENERS_DELETE: &str = "network:listeners:delete";

/// Convert domain Listener to proto
fn listener_to_proto(listener: &Listener) -> ProtoListener {
    ProtoListener {
        id: listener.id.to_string(),
        name: listener.name.clone(),
        loadbalancer_id: listener.loadbalancer_id.to_string(),
        protocol: match listener.protocol {
            ListenerProtocol::Tcp => ProtoListenerProtocol::Tcp.into(),
            ListenerProtocol::Udp => ProtoListenerProtocol::Udp.into(),
            ListenerProtocol::Http => ProtoListenerProtocol::Http.into(),
            ListenerProtocol::Https => ProtoListenerProtocol::Https.into(),
            ListenerProtocol::TerminatedHttps => ProtoListenerProtocol::TerminatedHttps.into(),
        },
        port: listener.port as u32,
        default_pool_id: listener.default_pool_id.map(|id| id.to_string()).unwrap_or_default(),
        tls_config: listener.tls_config.as_ref().map(|tls| {
            ProtoTlsConfig {
                certificate_id: tls.certificate_id.clone(),
                min_version: match tls.min_version {
                    TlsVersion::Tls12 => ProtoTlsVersion::Tls12.into(),
                    TlsVersion::Tls13 => ProtoTlsVersion::Tls13.into(),
                },
                cipher_suites: tls.cipher_suites.clone(),
            }
        }),
        connection_limit: listener.connection_limit,
        enabled: listener.enabled,
        created_at: listener.created_at,
        updated_at: listener.updated_at,
    }
}

/// Parse ListenerId from string
fn parse_listener_id(id: &str) -> Result<ListenerId, Status> {
    let uuid: Uuid = id
        .parse()
        .map_err(|_| Status::invalid_argument("invalid listener ID"))?;
    Ok(ListenerId::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))
}

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

/// Convert proto protocol to domain
fn proto_to_protocol(proto: i32) -> ListenerProtocol {
    match ProtoListenerProtocol::try_from(proto) {
        Ok(ProtoListenerProtocol::Tcp) => ListenerProtocol::Tcp,
        Ok(ProtoListenerProtocol::Udp) => ListenerProtocol::Udp,
        Ok(ProtoListenerProtocol::Http) => ListenerProtocol::Http,
        Ok(ProtoListenerProtocol::Https) => ListenerProtocol::Https,
        Ok(ProtoListenerProtocol::TerminatedHttps) => ListenerProtocol::TerminatedHttps,
        _ => ListenerProtocol::Tcp,
    }
}

/// Convert proto TLS config to domain
fn proto_to_tls_config(tls: Option<ProtoTlsConfig>) -> Option<TlsConfig> {
    tls.map(|t| {
        let min_version = match ProtoTlsVersion::try_from(t.min_version) {
            Ok(ProtoTlsVersion::Tls13) => TlsVersion::Tls13,
            _ => TlsVersion::Tls12,
        };
        TlsConfig {
            certificate_id: t.certificate_id,
            min_version,
            cipher_suites: t.cipher_suites,
        }
    })
}

#[tonic::async_trait]
impl ListenerService for ListenerServiceImpl {
    async fn create_listener(
        &self,
        request: Request<CreateListenerRequest>,
    ) -> Result<Response<CreateListenerResponse>, 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.port == 0 {
            return Err(Status::invalid_argument("port 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_LISTENERS_CREATE,
                &resource_for_tenant("listener", "*", &lb.org_id, &lb.project_id),
            )
            .await?;

        // Create new listener
        let protocol = proto_to_protocol(req.protocol);
        let mut listener = Listener::new(&req.name, lb_id, protocol, req.port as u16);

        // Apply optional settings
        if !req.default_pool_id.is_empty() {
            listener.default_pool_id = Some(parse_pool_id(&req.default_pool_id)?);
        }
        listener.tls_config = proto_to_tls_config(req.tls_config);
        if req.connection_limit > 0 {
            listener.connection_limit = req.connection_limit;
        }

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

        if let Err(e) = self.start_runtime_listener(&listener).await {
            self.metadata.delete_listener(&listener).await.ok();
            return Err(e);
        }

        Ok(Response::new(CreateListenerResponse {
            listener: Some(listener_to_proto(&listener)),
        }))
    }

    async fn get_listener(
        &self,
        request: Request<GetListenerRequest>,
    ) -> Result<Response<GetListenerResponse>, 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 listener_id = parse_listener_id(&req.id)?;

        let listener = self
            .metadata
            .load_listener_by_id(&listener_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("listener not found"))?;
        let lb = self
            .metadata
            .load_lb_by_id(&listener.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("listener not in tenant scope"));
        }

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

        Ok(Response::new(GetListenerResponse {
            listener: Some(listener_to_proto(&listener)),
        }))
    }

    async fn list_listeners(
        &self,
        request: Request<ListListenersRequest>,
    ) -> Result<Response<ListListenersResponse>, 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_LISTENERS_LIST,
                &resource_for_tenant("listener", "*", &lb.org_id, &lb.project_id),
            )
            .await?;

        let listeners = self
            .metadata
            .list_listeners(&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 = listeners.len();
        let end = std::cmp::min(offset + page_size, total);
        let paginated = listeners.iter().skip(offset).take(page_size);

        let proto_listeners: Vec<ProtoListener> = paginated.map(listener_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(ListListenersResponse {
            listeners: proto_listeners,
            next_page_token,
        }))
    }

    async fn update_listener(
        &self,
        request: Request<UpdateListenerRequest>,
    ) -> Result<Response<UpdateListenerResponse>, 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 listener_id = parse_listener_id(&req.id)?;

        let mut listener = self
            .metadata
            .load_listener_by_id(&listener_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("listener not found"))?;
        let lb = self
            .metadata
            .load_lb_by_id(&listener.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("listener not in tenant scope"));
        }
        let lb_org_id = lb.org_id;
        let lb_project_id = lb.project_id;

        self.auth
            .authorize(
                &tenant,
                ACTION_LISTENERS_UPDATE,
                &resource_for_tenant("listener", &listener.id.to_string(), &lb_org_id, &lb_project_id),
            )
            .await?;

        let previous_listener = listener.clone();
        if previous_listener.enabled {
            self.stop_runtime_listener(&previous_listener).await;
        }

        // Apply updates
        if !req.name.is_empty() {
            listener.name = req.name;
        }
        if !req.default_pool_id.is_empty() {
            listener.default_pool_id = Some(parse_pool_id(&req.default_pool_id)?);
        }
        if req.tls_config.is_some() {
            listener.tls_config = proto_to_tls_config(req.tls_config);
        }
        if req.connection_limit > 0 {
            listener.connection_limit = req.connection_limit;
        }
        listener.enabled = req.enabled;

        // Update timestamp
        listener.updated_at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

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

        if let Err(e) = self.start_runtime_listener(&listener).await {
            self.metadata.save_listener(&previous_listener).await.ok();
            if previous_listener.enabled {
                self.start_runtime_listener(&previous_listener).await.ok();
            }
            return Err(e);
        }

        Ok(Response::new(UpdateListenerResponse {
            listener: Some(listener_to_proto(&listener)),
        }))
    }

    async fn delete_listener(
        &self,
        request: Request<DeleteListenerRequest>,
    ) -> Result<Response<DeleteListenerResponse>, 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 listener_id = parse_listener_id(&req.id)?;

        let listener = self
            .metadata
            .load_listener_by_id(&listener_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("listener not found"))?;
        let lb = self
            .metadata
            .load_lb_by_id(&listener.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("listener not in tenant scope"));
        }
        let lb_org_id = lb.org_id;
        let lb_project_id = lb.project_id;

        self.auth
            .authorize(
                &tenant,
                ACTION_LISTENERS_DELETE,
                &resource_for_tenant("listener", &listener.id.to_string(), &lb_org_id, &lb_project_id),
            )
            .await?;

        self.stop_runtime_listener(&listener).await;

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

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