//! Core Services Module
//!
//! Contains models for Oracle Cloud Infrastructure Core Services:
//! - Compute (instances, images, shapes)
//! - VirtualNetwork (VCNs, subnets, security lists)
//! - Blockstorage (volumes, backups)

pub mod client;
pub mod error;
pub mod models;
pub mod region;
pub mod requests;
pub mod responses;
pub mod retry;

// Re-exports for convenience
pub use error::{OciError, Result};
pub use models::*;
pub use region::Region;
pub use requests::*;
pub use responses::*;
pub use retry::{Retrier, RetryConfiguration};

use crate::auth::provider::AuthProvider;
use client::http_client::OciClient;
use std::sync::Arc;

/// Client configuration for core services
pub struct ClientConfig {
    pub auth_provider: Arc<dyn AuthProvider>,
    pub region: Region,
    pub timeout: std::time::Duration,
    pub retry: Retrier,
}

/// Create a new core services client
pub fn client(config: ClientConfig) -> Result<CoreClient> {
    let endpoint = format!("https://iaas.{}.oraclecloud.com", config.region.id());
    let client = OciClient::with_timeout(config.auth_provider, endpoint, config.timeout)?.with_retrier(config.retry);

    Ok(CoreClient { client })
}

#[async_trait::async_trait]
pub trait CoreApi: Send + Sync {
    async fn list_instances(
        &self,
        request: ListInstancesRequest,
    ) -> Result<ListInstancesResponse>;

    async fn launch_instance(
        &self,
        request: LaunchInstanceRequest,
    ) -> Result<LaunchInstanceResponse>;

    async fn get_instance(&self, request: GetInstanceRequest) -> Result<GetInstanceResponse>;

    async fn terminate_instance(
        &self,
        request: TerminateInstanceRequest,
    ) -> Result<TerminateInstanceResponse>;

    async fn list_vnic_attachments(
        &self,
        request: ListVnicAttachmentsRequest,
    ) -> Result<ListVnicAttachmentsResponse>;

    async fn get_vnic(&self, request: GetVnicRequest) -> Result<GetVnicResponse>;

    async fn list_public_ips(
        &self,
        request: ListPublicIpsRequest,
    ) -> Result<ListPublicIpsResponse>;
}

/// Core Services API client
pub struct CoreClient {
    client: OciClient,
}

#[async_trait::async_trait]
impl CoreApi for CoreClient {
    async fn list_instances(
        &self,
        request: ListInstancesRequest,
    ) -> Result<ListInstancesResponse> {
        let mut query_params = vec![("compartmentId".to_string(), request.compartment_id.clone())];

        if let Some(av_domain) = &request.availability_domain {
            query_params.push(("availabilityDomain".to_string(), av_domain.clone()));
        }
        if let Some(display_name) = &request.display_name {
            query_params.push(("displayName".to_string(), display_name.clone()));
        }
        if let Some(limit) = request.limit {
            query_params.push(("limit".to_string(), limit.to_string()));
        }
        if let Some(page) = &request.page {
            query_params.push(("page".to_string(), page.clone()));
        }
        if let Some(lifecycle_state) = &request.lifecycle_state {
            query_params.push(("lifecycleState".to_string(), lifecycle_state.clone()));
        }

        let query_string = if query_params.is_empty() {
            String::new()
        } else {
            format!(
                "?{}",
                query_params
                    .iter()
                    .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
                    .collect::<Vec<_>>()
                    .join("&")
            )
        };

        let path = format!("/20160918/instances{}", query_string);
        let response = self.client.get(&path).await?;

        Ok(ListInstancesResponse {
            opc_next_page: response.get_header("opc-next-page"),
            opc_request_id: response.get_header("opc-request-id"),
            items: response.body,
        })
    }

    async fn launch_instance(
        &self,
        request: LaunchInstanceRequest,
    ) -> Result<LaunchInstanceResponse> {
        let path = "/20160918/instances".to_string();
        let response = self
            .client
            .post(&path, Some(&request.launch_instance_details))
            .await?;

        Ok(LaunchInstanceResponse {
            etag: response.get_header("etag"),
            opc_request_id: response.get_header("opc-request-id"),
            opc_work_request_id: response.get_header("opc-work-request-id"),
            instance: response.body,
        })
    }

    async fn get_instance(&self, request: GetInstanceRequest) -> Result<GetInstanceResponse> {
        let path = format!("/20160918/instances/{}", request.instance_id);
        let response = self.client.get(&path).await?;

        Ok(GetInstanceResponse {
            etag: response.get_header("etag"),
            opc_request_id: response.get_header("opc-request-id"),
            instance: response.body,
        })
    }

    async fn terminate_instance(
        &self,
        request: TerminateInstanceRequest,
    ) -> Result<TerminateInstanceResponse> {
        let mut query_params = Vec::new();
        if let Some(preserve_boot_volume) = request.preserve_boot_volume {
            query_params.push(("preserveBootVolume".to_string(), preserve_boot_volume.to_string()));
        }

        let query_string = if query_params.is_empty() {
            String::new()
        } else {
            format!(
                "?{}",
                query_params
                    .iter()
                    .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
                    .collect::<Vec<_>>()
                    .join("&")
            )
        };

        let path = format!("/20160918/instances/{}{}", request.instance_id, query_string);
        let response: client::http_client::OciResponse<serde_json::Value> =
            self.client.delete(&path).await?;

        Ok(TerminateInstanceResponse {
            opc_request_id: response.get_header("opc-request-id").unwrap_or_default(),
        })
    }

    async fn list_vnic_attachments(
        &self,
        request: ListVnicAttachmentsRequest,
    ) -> Result<ListVnicAttachmentsResponse> {
        let mut query_params = vec![("compartmentId".to_string(), request.compartment_id.clone())];

        if let Some(instance_id) = &request.instance_id {
            query_params.push(("instanceId".to_string(), instance_id.clone()));
        }
        if let Some(limit) = request.limit {
            query_params.push(("limit".to_string(), limit.to_string()));
        }
        if let Some(page) = &request.page {
            query_params.push(("page".to_string(), page.clone()));
        }

        let query_string = format!(
            "?{}",
            query_params
                .iter()
                .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
                .collect::<Vec<_>>()
                .join("&")
        );

        let path = format!("/20160918/vnicAttachments{}", query_string);
        let response = self.client.get(&path).await?;

        Ok(ListVnicAttachmentsResponse {
            opc_next_page: response.get_header("opc-next-page").unwrap_or_default(),
            opc_request_id: response.get_header("opc-request-id").unwrap_or_default(),
            items: response.body,
        })
    }

    async fn get_vnic(&self, request: GetVnicRequest) -> Result<GetVnicResponse> {
        let path = format!("/20160918/vnics/{}", request.vnic_id);
        let response = self.client.get(&path).await?;

        Ok(GetVnicResponse {
            etag: response.get_header("etag").unwrap_or_default(),
            opc_request_id: response.get_header("opc-request-id").unwrap_or_default(),
            vnic: response.body,
        })
    }

    async fn list_public_ips(
        &self,
        request: ListPublicIpsRequest,
    ) -> Result<ListPublicIpsResponse> {
        let mut query_params = vec![("scope".to_string(), format!("{:?}", request.scope))];
        query_params.push(("compartmentId".to_string(), request.compartment_id.clone()));

        if let Some(limit) = request.limit {
            query_params.push(("limit".to_string(), limit.to_string()));
        }
        if let Some(page) = &request.page {
            query_params.push(("page".to_string(), page.clone()));
        }
        if let Some(availability_domain) = &request.availability_domain {
            query_params.push((
                "availabilityDomain".to_string(),
                availability_domain.clone(),
            ));
        }
        if let Some(lifetime) = &request.lifetime {
            query_params.push(("lifetime".to_string(), format!("{:?}", lifetime)));
        }

        let query_string = format!(
            "?{}",
            query_params
                .iter()
                .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
                .collect::<Vec<_>>()
                .join("&")
        );

        let path = format!("/20160918/publicIps{}", query_string);
        let response = self.client.get(&path).await?;

        Ok(ListPublicIpsResponse {
            opc_next_page: response.get_header("opc-next-page"),
            opc_request_id: response.get_header("opc-request-id"),
            items: response.body,
        })
    }
}
