//! LoadBalancer service implementation

use std::net::IpAddr;
use std::sync::Arc;

use base64::Engine as _;
use crate::metadata::LbMetadataStore;
use fiberlb_api::{
    load_balancer_service_server::LoadBalancerService,
    CreateLoadBalancerRequest, CreateLoadBalancerResponse,
    DeleteLoadBalancerRequest, DeleteLoadBalancerResponse,
    GetLoadBalancerRequest, GetLoadBalancerResponse,
    ListLoadBalancersRequest, ListLoadBalancersResponse,
    UpdateLoadBalancerRequest, UpdateLoadBalancerResponse,
    LoadBalancer as ProtoLoadBalancer, LoadBalancerStatus as ProtoLoadBalancerStatus,
};
use fiberlb_types::{LoadBalancer, LoadBalancerId, LoadBalancerStatus};
use iam_service_auth::{get_tenant_context, resolve_tenant_ids_from_context, resource_for_tenant, AuthService};
use tonic::{Request, Response, Status};
use uuid::Uuid;

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

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

fn normalize_requested_vip(vip_address: &str) -> Result<Option<String>, Status> {
    let trimmed = vip_address.trim();
    if trimmed.is_empty() {
        return Ok(None);
    }

    let vip: IpAddr = trimmed
        .parse()
        .map_err(|_| Status::invalid_argument("vip_address must be a valid IP address"))?;

    if vip.is_unspecified() || vip.is_multicast() {
        return Err(Status::invalid_argument(
            "vip_address must be a usable unicast address",
        ));
    }

    Ok(Some(vip.to_string()))
}

async fn ensure_vip_available(metadata: &LbMetadataStore, vip: &str) -> Result<(), Status> {
    let lbs = metadata
        .list_all_lbs()
        .await
        .map_err(|e| Status::internal(format!("metadata error: {}", e)))?;

    if lbs
        .iter()
        .any(|lb| lb.vip_address.as_deref() == Some(vip))
    {
        return Err(Status::already_exists(format!(
            "vip_address {} is already in use",
            vip
        )));
    }

    Ok(())
}

const ACTION_LB_CREATE: &str = "network:loadbalancers:create";
const ACTION_LB_READ: &str = "network:loadbalancers:read";
const ACTION_LB_LIST: &str = "network:loadbalancers:list";
const ACTION_LB_UPDATE: &str = "network:loadbalancers:update";
const ACTION_LB_DELETE: &str = "network:loadbalancers:delete";

/// Convert domain LoadBalancer to proto
fn lb_to_proto(lb: &LoadBalancer) -> ProtoLoadBalancer {
    ProtoLoadBalancer {
        id: lb.id.to_string(),
        name: lb.name.clone(),
        org_id: lb.org_id.clone(),
        project_id: lb.project_id.clone(),
        description: lb.description.clone().unwrap_or_default(),
        status: match lb.status {
            LoadBalancerStatus::Provisioning => ProtoLoadBalancerStatus::Provisioning.into(),
            LoadBalancerStatus::Active => ProtoLoadBalancerStatus::Active.into(),
            LoadBalancerStatus::Updating => ProtoLoadBalancerStatus::Updating.into(),
            LoadBalancerStatus::Error => ProtoLoadBalancerStatus::Error.into(),
            LoadBalancerStatus::Deleting => ProtoLoadBalancerStatus::Deleting.into(),
        },
        vip_address: lb.vip_address.clone().unwrap_or_default(),
        created_at: lb.created_at,
        updated_at: lb.updated_at,
    }
}

/// 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))
}

#[tonic::async_trait]
impl LoadBalancerService for LoadBalancerServiceImpl {
    async fn create_load_balancer(
        &self,
        request: Request<CreateLoadBalancerRequest>,
    ) -> Result<Response<CreateLoadBalancerResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();
        let (org_id, project_id) =
            resolve_tenant_ids_from_context(&tenant, &req.org_id, &req.project_id)?;

        self.auth
            .authorize(
                &tenant,
                ACTION_LB_CREATE,
                &resource_for_tenant("loadbalancer", "*", &org_id, &project_id),
            )
            .await?;

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

        // Create new load balancer
        let mut lb = LoadBalancer::new(&req.name, &org_id, &project_id);

        // Apply optional description
        if !req.description.is_empty() {
            lb.description = Some(req.description);
        }

        let requested_vip = normalize_requested_vip(&req.vip_address)?;
        let vip = if let Some(vip) = requested_vip {
            ensure_vip_available(&self.metadata, &vip).await?;
            vip
        } else {
            self.metadata
                .allocate_vip()
                .await
                .map_err(|e| Status::resource_exhausted(format!("failed to allocate VIP: {}", e)))?
        };
        lb.vip_address = Some(vip);

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

        Ok(Response::new(CreateLoadBalancerResponse {
            loadbalancer: Some(lb_to_proto(&lb)),
        }))
    }

    async fn get_load_balancer(
        &self,
        request: Request<GetLoadBalancerRequest>,
    ) -> Result<Response<GetLoadBalancerResponse>, 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 lb_id = parse_lb_id(&req.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_LB_READ,
                &resource_for_tenant("loadbalancer", &lb.id.to_string(), &lb.org_id, &lb.project_id),
            )
            .await?;

        Ok(Response::new(GetLoadBalancerResponse {
            loadbalancer: Some(lb_to_proto(&lb)),
        }))
    }

    async fn list_load_balancers(
        &self,
        request: Request<ListLoadBalancersRequest>,
    ) -> Result<Response<ListLoadBalancersResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();
        let (org_id, project_id) =
            resolve_tenant_ids_from_context(&tenant, &req.org_id, &req.project_id)?;

        self.auth
            .authorize(
                &tenant,
                ACTION_LB_LIST,
                &resource_for_tenant("loadbalancer", "*", &org_id, &project_id),
            )
            .await?;

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

        let loadbalancers: Vec<ProtoLoadBalancer> = paginated.map(lb_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(ListLoadBalancersResponse {
            loadbalancers,
            next_page_token,
        }))
    }

    async fn update_load_balancer(
        &self,
        request: Request<UpdateLoadBalancerRequest>,
    ) -> Result<Response<UpdateLoadBalancerResponse>, 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 lb_id = parse_lb_id(&req.id)?;

        let mut 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_LB_UPDATE,
                &resource_for_tenant("loadbalancer", &lb.id.to_string(), &lb.org_id, &lb.project_id),
            )
            .await?;

        // Apply updates
        if !req.name.is_empty() {
            lb.name = req.name;
        }
        if !req.description.is_empty() {
            lb.description = Some(req.description);
        }

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

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

        Ok(Response::new(UpdateLoadBalancerResponse {
            loadbalancer: Some(lb_to_proto(&lb)),
        }))
    }

    async fn delete_load_balancer(
        &self,
        request: Request<DeleteLoadBalancerRequest>,
    ) -> Result<Response<DeleteLoadBalancerResponse>, 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 lb_id = parse_lb_id(&req.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_LB_DELETE,
                &resource_for_tenant("loadbalancer", &lb.id.to_string(), &lb.org_id, &lb.project_id),
            )
            .await?;

        // Delete all associated resources (cascade delete)
        self.metadata
            .delete_lb_listeners(&lb.id)
            .await
            .map_err(|e| Status::internal(format!("failed to delete listeners: {}", e)))?;

        self.metadata
            .delete_lb_pools(&lb.id)
            .await
            .map_err(|e| Status::internal(format!("failed to delete pools: {}", e)))?;

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

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