//! ReverseZoneService gRPC implementation

use std::net::IpAddr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::dns::ptr_patterns::apply_pattern;
use crate::metadata::DnsMetadataStore;
use flashdns_api::proto::{
    CreateReverseZoneRequest, DeleteReverseZoneRequest, DeleteReverseZoneResponse,
    GetReverseZoneRequest, ListReverseZonesRequest, ListReverseZonesResponse,
    ResolvePtrForIpRequest, ResolvePtrForIpResponse, ReverseZone as ProtoReverseZone,
};
use flashdns_api::ReverseZoneService;
use flashdns_types::ReverseZone;
use iam_service_auth::{get_tenant_context, resolve_tenant_ids_from_context, resource_for_tenant, AuthService};
use ipnet::IpNet;
use tonic::{Request, Response, Status};
use uuid::Uuid;

/// ReverseZoneService implementation
pub struct ReverseZoneServiceImpl {
    metadata: Arc<DnsMetadataStore>,
    auth: Arc<AuthService>,
}

impl ReverseZoneServiceImpl {
    /// Create a new ReverseZoneService with metadata store
    pub fn new(metadata: Arc<DnsMetadataStore>, auth: Arc<AuthService>) -> Self {
        Self { metadata, auth }
    }
}

const ACTION_REVERSE_ZONES_CREATE: &str = "dns:reverse-zones:create";
const ACTION_REVERSE_ZONES_READ: &str = "dns:reverse-zones:read";
const ACTION_REVERSE_ZONES_DELETE: &str = "dns:reverse-zones:delete";
const ACTION_REVERSE_ZONES_LIST: &str = "dns:reverse-zones:list";

fn reverse_zone_to_proto(zone: &ReverseZone) -> ProtoReverseZone {
    ProtoReverseZone {
        id: zone.id.clone(),
        org_id: zone.org_id.clone(),
        project_id: zone.project_id.clone(),
        cidr: zone.cidr.clone(),
        arpa_zone: zone.arpa_zone.clone(),
        ptr_pattern: zone.ptr_pattern.clone(),
        ttl: zone.ttl,
        created_at: zone.created_at,
        updated_at: zone.updated_at,
    }
}

fn now_epoch() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

fn find_reverse_zone_for_ip(zones: &[ReverseZone], ip: IpAddr) -> Option<ReverseZone> {
    let mut best_match: Option<ReverseZone> = None;
    let mut best_prefix_len = 0;

    for zone in zones {
        if let Ok(cidr) = zone.cidr.parse::<IpNet>() {
            if cidr.contains(&ip) {
                let prefix_len = cidr.prefix_len();
                if prefix_len > best_prefix_len {
                    best_prefix_len = prefix_len;
                    best_match = Some(zone.clone());
                }
            }
        }
    }

    best_match
}

#[tonic::async_trait]
impl ReverseZoneService for ReverseZoneServiceImpl {
    async fn create_reverse_zone(
        &self,
        request: Request<CreateReverseZoneRequest>,
    ) -> Result<Response<ProtoReverseZone>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();
        let req_project_id = req.project_id.clone().unwrap_or_default();
        let (org_id, project_id) =
            resolve_tenant_ids_from_context(&tenant, &req.org_id, &req_project_id)?;

        self.auth
            .authorize(
                &tenant,
                ACTION_REVERSE_ZONES_CREATE,
                &resource_for_tenant("reverse-zone", "*", &org_id, &project_id),
            )
            .await?;

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

        let existing = self
            .metadata
            .list_reverse_zones(&org_id, Some(project_id.as_str()))
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?;

        if existing.iter().any(|zone| zone.cidr == req.cidr) {
            return Err(Status::already_exists("reverse zone already exists"));
        }

        let now = now_epoch();
        let mut zone = ReverseZone {
            id: Uuid::new_v4().to_string(),
            org_id,
            project_id: Some(project_id),
            cidr: req.cidr,
            arpa_zone: String::new(),
            ptr_pattern: req.ptr_pattern,
            ttl: if req.ttl == 0 { 3600 } else { req.ttl },
            created_at: now,
            updated_at: now,
        };

        zone = self
            .metadata
            .create_reverse_zone(zone)
            .await
            .map_err(|e| Status::internal(format!("failed to save reverse zone: {}", e)))?;

        Ok(Response::new(reverse_zone_to_proto(&zone)))
    }

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

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

        let zone = self
            .metadata
            .get_reverse_zone(&req.zone_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("reverse zone not found"))?;

        if zone.org_id != tenant.org_id || zone.project_id.as_deref() != Some(tenant.project_id.as_str()) {
            return Err(Status::permission_denied("reverse zone not in tenant scope"));
        }

        self.auth
            .authorize(
                &tenant,
                ACTION_REVERSE_ZONES_READ,
                &resource_for_tenant("reverse-zone", &zone.id, &zone.org_id, tenant.project_id.as_str()),
            )
            .await?;

        Ok(Response::new(reverse_zone_to_proto(&zone)))
    }

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

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

        let zone = self
            .metadata
            .get_reverse_zone(&req.zone_id)
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?
            .ok_or_else(|| Status::not_found("reverse zone not found"))?;

        if zone.org_id != tenant.org_id || zone.project_id.as_deref() != Some(tenant.project_id.as_str()) {
            return Err(Status::permission_denied("reverse zone not in tenant scope"));
        }

        self.auth
            .authorize(
                &tenant,
                ACTION_REVERSE_ZONES_DELETE,
                &resource_for_tenant("reverse-zone", &zone.id, &zone.org_id, tenant.project_id.as_str()),
            )
            .await?;

        self.metadata
            .delete_reverse_zone(&zone)
            .await
            .map_err(|e| Status::internal(format!("failed to delete reverse zone: {}", e)))?;

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

    async fn list_reverse_zones(
        &self,
        request: Request<ListReverseZonesRequest>,
    ) -> Result<Response<ListReverseZonesResponse>, Status> {
        let tenant = get_tenant_context(&request)?;
        let req = request.into_inner();
        let req_project_id = req.project_id.clone().unwrap_or_default();
        let (org_id, project_id) =
            resolve_tenant_ids_from_context(&tenant, &req.org_id, &req_project_id)?;

        self.auth
            .authorize(
                &tenant,
                ACTION_REVERSE_ZONES_LIST,
                &resource_for_tenant("reverse-zone", "*", &org_id, &project_id),
            )
            .await?;

        let zones = self
            .metadata
            .list_reverse_zones(&org_id, Some(project_id.as_str()))
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?;

        let proto_zones = zones.iter().map(reverse_zone_to_proto).collect();

        Ok(Response::new(ListReverseZonesResponse { zones: proto_zones }))
    }

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

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

        let ip: IpAddr = req
            .ip_address
            .parse()
            .map_err(|_| Status::invalid_argument("invalid ip_address"))?;

        self.auth
            .authorize(
                &tenant,
                ACTION_REVERSE_ZONES_READ,
                &resource_for_tenant("reverse-zone", "*", &tenant.org_id, &tenant.project_id),
            )
            .await?;

        let zones = self
            .metadata
            .list_all_reverse_zones()
            .await
            .map_err(|e| Status::internal(format!("metadata error: {}", e)))?;

        let scoped_zones: Vec<_> = zones
            .into_iter()
            .filter(|zone| {
                zone.org_id == tenant.org_id
                    && zone.project_id.as_deref() == Some(tenant.project_id.as_str())
            })
            .collect();

        if let Some(zone) = find_reverse_zone_for_ip(&scoped_zones, ip) {
            let ptr_value = apply_pattern(&zone.ptr_pattern, ip);
            return Ok(Response::new(ResolvePtrForIpResponse {
                ptr_record: Some(ptr_value),
                reverse_zone_id: Some(zone.id),
                found: true,
            }));
        }

        Ok(Response::new(ResolvePtrForIpResponse {
            ptr_record: None,
            reverse_zone_id: None,
            found: false,
        }))
    }
}
