//! DNS record management for Cloudflare.
//!
//! This module provides comprehensive DNS record management capabilities including:
//! - Creating, reading, updating, and deleting DNS records
//! - Listing and filtering records
//! - Batch operations with diff output
//! - Idempotent sync operations for CI/CD

use crate::client::CloudflareClient;
use crate::error::Result;
use crate::types::Change;

pub mod types;
pub use types::{DnsRecord, DnsRecordBuilder, ListRecordsQuery, RecordType};

/// Service for managing DNS records.
///
/// Obtain an instance via [`CloudflareClient::dns()`].
#[derive(Clone)]
pub struct DnsService {
    client: CloudflareClient,
}

impl DnsService {
    /// Create a new DNS service (internal use).
    pub(crate) fn new(client: CloudflareClient) -> Self {
        Self { client }
    }

    /// List DNS records for a zone.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # use lmrc_cloudflare::dns::RecordType;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// // List all records
    /// let all_records = client.dns()
    ///     .list_records("zone_id")
    ///     .send()
    ///     .await?;
    ///
    /// // Filter by type
    /// let a_records = client.dns()
    ///     .list_records("zone_id")
    ///     .record_type(RecordType::A)
    ///     .send()
    ///     .await?;
    ///
    /// // Filter by name and type
    /// let records = client.dns()
    ///     .list_records("zone_id")
    ///     .name("api.example.com")
    ///     .record_type(RecordType::A)
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn list_records(&self, zone_id: impl Into<String>) -> ListRecordsRequest {
        ListRecordsRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            query: ListRecordsQuery::new(),
        }
    }

    /// Get a specific DNS record by ID.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let record = client.dns()
    ///     .get_record("zone_id", "record_id")
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_record(
        &self,
        zone_id: impl Into<String>,
        record_id: impl Into<String>,
    ) -> Result<DnsRecord> {
        let zone_id = zone_id.into();
        let record_id = record_id.into();

        let response = self
            .client
            .get(&format!("/zones/{}/dns_records/{}", zone_id, record_id))
            .await?;

        CloudflareClient::handle_response(response).await
    }

    /// Create a new DNS record.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # use lmrc_cloudflare::dns::RecordType;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let record = client.dns()
    ///     .create_record("zone_id")
    ///     .name("api")
    ///     .record_type(RecordType::A)
    ///     .content("192.0.2.1")
    ///     .proxied(true)
    ///     .ttl(1)
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn create_record(&self, zone_id: impl Into<String>) -> CreateRecordRequest {
        CreateRecordRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            builder: DnsRecordBuilder::new(),
        }
    }

    /// Update an existing DNS record.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let record = client.dns()
    ///     .update_record("zone_id", "record_id")
    ///     .content("192.0.2.2")
    ///     .proxied(false)
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn update_record(
        &self,
        zone_id: impl Into<String>,
        record_id: impl Into<String>,
    ) -> UpdateRecordRequest {
        UpdateRecordRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            record_id: record_id.into(),
            builder: DnsRecordBuilder::new(),
        }
    }

    /// Delete a DNS record.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.dns()
    ///     .delete_record("zone_id", "record_id")
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn delete_record(
        &self,
        zone_id: impl Into<String>,
        record_id: impl Into<String>,
    ) -> Result<()> {
        let zone_id = zone_id.into();
        let record_id = record_id.into();

        let response = self
            .client
            .delete(&format!("/zones/{}/dns_records/{}", zone_id, record_id))
            .await?;

        let _: serde_json::Value = CloudflareClient::handle_response(response).await?;
        Ok(())
    }

    /// Find a DNS record by name and type.
    ///
    /// Returns `None` if no matching record is found.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # use lmrc_cloudflare::dns::RecordType;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let record = client.dns()
    ///     .find_record("zone_id", "api.example.com", RecordType::A)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn find_record(
        &self,
        zone_id: impl Into<String>,
        name: impl Into<String>,
        record_type: RecordType,
    ) -> Result<Option<DnsRecord>> {
        let name = name.into();
        let records = self
            .list_records(zone_id)
            .name(&name)
            .record_type(record_type)
            .send()
            .await?;

        Ok(records.into_iter().find(|r| r.matches(&name, record_type)))
    }

    /// Sync DNS records with desired state, showing diff of changes.
    ///
    /// This is an idempotent operation that will:
    /// - Create records that don't exist
    /// - Update records that have changed
    /// - Leave unchanged records alone
    ///
    /// Returns a list of changes that were made or would be made.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # use lmrc_cloudflare::dns::{RecordType, DnsRecordBuilder};
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let desired_records = vec![
    ///     DnsRecordBuilder::new()
    ///         .name("api.example.com")
    ///         .record_type(RecordType::A)
    ///         .content("192.0.2.1")
    ///         .proxied(true),
    ///     DnsRecordBuilder::new()
    ///         .name("www.example.com")
    ///         .record_type(RecordType::CNAME)
    ///         .content("example.com")
    ///         .proxied(true),
    /// ];
    ///
    /// let changes = client.dns()
    ///     .sync_records("zone_id")
    ///     .records(desired_records)
    ///     .dry_run(false)
    ///     .send()
    ///     .await?;
    ///
    /// for change in changes {
    ///     println!("{:?}: {}", change.action, change.description);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub fn sync_records(&self, zone_id: impl Into<String>) -> SyncRecordsRequest {
        SyncRecordsRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            records: Vec::new(),
            dry_run: false,
        }
    }
}

/// Request builder for listing DNS records.
pub struct ListRecordsRequest {
    service: DnsService,
    zone_id: String,
    query: ListRecordsQuery,
}

impl ListRecordsRequest {
    /// Filter by record type.
    pub fn record_type(mut self, record_type: RecordType) -> Self {
        self.query = self.query.record_type(record_type);
        self
    }

    /// Filter by record name.
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.query = self.query.name(name);
        self
    }

    /// Filter by content.
    pub fn content(mut self, content: impl Into<String>) -> Self {
        self.query = self.query.content(content);
        self
    }

    /// Set page number for pagination.
    pub fn page(mut self, page: u32) -> Self {
        self.query = self.query.page(page);
        self
    }

    /// Set number of results per page.
    pub fn per_page(mut self, per_page: u32) -> Self {
        self.query = self.query.per_page(per_page);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<Vec<DnsRecord>> {
        let params = self.query.build_params();
        let response = self
            .service
            .client
            .get_with_params(&format!("/zones/{}/dns_records", self.zone_id), &params)
            .await?;

        CloudflareClient::handle_response(response).await
    }
}

/// Request builder for creating DNS records.
pub struct CreateRecordRequest {
    service: DnsService,
    zone_id: String,
    builder: DnsRecordBuilder,
}

impl CreateRecordRequest {
    /// Set the record name.
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.builder = self.builder.name(name);
        self
    }

    /// Set the record type.
    pub fn record_type(mut self, record_type: RecordType) -> Self {
        self.builder = self.builder.record_type(record_type);
        self
    }

    /// Set the record content/value.
    pub fn content(mut self, content: impl Into<String>) -> Self {
        self.builder = self.builder.content(content);
        self
    }

    /// Set whether the record should be proxied.
    pub fn proxied(mut self, proxied: bool) -> Self {
        self.builder = self.builder.proxied(proxied);
        self
    }

    /// Set the TTL.
    pub fn ttl(mut self, ttl: u32) -> Self {
        self.builder = self.builder.ttl(ttl);
        self
    }

    /// Set a comment.
    pub fn comment(mut self, comment: impl Into<String>) -> Self {
        self.builder = self.builder.comment(comment);
        self
    }

    /// Set the priority (for MX, SRV records).
    pub fn priority(mut self, priority: u16) -> Self {
        self.builder = self.builder.priority(priority);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<DnsRecord> {
        self.builder.validate_create()?;

        let payload = self.builder.build_payload();
        let response = self
            .service
            .client
            .post(&format!("/zones/{}/dns_records", self.zone_id), &payload)
            .await?;

        CloudflareClient::handle_response(response).await
    }
}

/// Request builder for updating DNS records.
pub struct UpdateRecordRequest {
    service: DnsService,
    zone_id: String,
    record_id: String,
    builder: DnsRecordBuilder,
}

impl UpdateRecordRequest {
    /// Set the record name.
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.builder = self.builder.name(name);
        self
    }

    /// Set the record type.
    pub fn record_type(mut self, record_type: RecordType) -> Self {
        self.builder = self.builder.record_type(record_type);
        self
    }

    /// Set the record content/value.
    pub fn content(mut self, content: impl Into<String>) -> Self {
        self.builder = self.builder.content(content);
        self
    }

    /// Set whether the record should be proxied.
    pub fn proxied(mut self, proxied: bool) -> Self {
        self.builder = self.builder.proxied(proxied);
        self
    }

    /// Set the TTL.
    pub fn ttl(mut self, ttl: u32) -> Self {
        self.builder = self.builder.ttl(ttl);
        self
    }

    /// Set a comment.
    pub fn comment(mut self, comment: impl Into<String>) -> Self {
        self.builder = self.builder.comment(comment);
        self
    }

    /// Set the priority (for MX, SRV records).
    pub fn priority(mut self, priority: u16) -> Self {
        self.builder = self.builder.priority(priority);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<DnsRecord> {
        let payload = self.builder.build_payload();
        let response = self
            .service
            .client
            .put(
                &format!("/zones/{}/dns_records/{}", self.zone_id, self.record_id),
                &payload,
            )
            .await?;

        CloudflareClient::handle_response(response).await
    }
}

/// Request builder for syncing DNS records.
pub struct SyncRecordsRequest {
    service: DnsService,
    zone_id: String,
    records: Vec<DnsRecordBuilder>,
    dry_run: bool,
}

impl SyncRecordsRequest {
    /// Set the desired records.
    pub fn records(mut self, records: Vec<DnsRecordBuilder>) -> Self {
        self.records = records;
        self
    }

    /// Add a single desired record.
    pub fn add_record(mut self, record: DnsRecordBuilder) -> Self {
        self.records.push(record);
        self
    }

    /// Set whether this is a dry run (don't actually make changes).
    pub fn dry_run(mut self, dry_run: bool) -> Self {
        self.dry_run = dry_run;
        self
    }

    /// Send the request and return the list of changes.
    pub async fn send(self) -> Result<Vec<Change<DnsRecord>>> {
        let mut changes = Vec::new();

        // Get all existing records for this zone
        let existing_records = self.service.list_records(&self.zone_id).send().await?;

        for desired in &self.records {
            desired.validate_create()?;

            let name = desired.name.as_ref().unwrap();
            let record_type = desired.record_type.unwrap();
            let content = desired.content.as_ref().unwrap();

            // Find existing record
            let existing = existing_records
                .iter()
                .find(|r| r.matches(name, record_type));

            match existing {
                Some(existing_record) => {
                    // Check if update is needed
                    if existing_record.needs_update(desired) {
                        let change = Change::update(
                            existing_record.clone(),
                            existing_record.clone(), // Would be updated version
                            format!(
                                "Update {} record: {} -> {}",
                                record_type.as_str(),
                                name,
                                content
                            ),
                        );
                        changes.push(change);

                        if !self.dry_run {
                            self.service
                                .update_record(&self.zone_id, &existing_record.id)
                                .content(content)
                                .proxied(desired.proxied.unwrap_or(existing_record.proxied))
                                .ttl(desired.ttl.unwrap_or(existing_record.ttl))
                                .send()
                                .await?;
                        }
                    } else {
                        let change = Change::no_change(
                            existing_record.clone(),
                            format!("{} record already correct: {}", record_type.as_str(), name),
                        );
                        changes.push(change);
                    }
                }
                None => {
                    // Create new record
                    let change = Change::create(
                        DnsRecord {
                            id: String::new(),
                            record_type: record_type.as_str().to_string(),
                            name: name.clone(),
                            content: content.clone(),
                            proxied: desired.proxied.unwrap_or(true),
                            ttl: desired.ttl.unwrap_or(1),
                            zone_id: self.zone_id.clone(),
                            zone_name: String::new(),
                            created_on: None,
                            modified_on: None,
                            comment: desired.comment.clone(),
                            priority: desired.priority,
                        },
                        format!(
                            "Create {} record: {} -> {}",
                            record_type.as_str(),
                            name,
                            content
                        ),
                    );
                    changes.push(change);

                    if !self.dry_run {
                        self.service
                            .create_record(&self.zone_id)
                            .name(name)
                            .record_type(record_type)
                            .content(content)
                            .proxied(desired.proxied.unwrap_or(true))
                            .ttl(desired.ttl.unwrap_or(1))
                            .send()
                            .await?;
                    }
                }
            }
        }

        Ok(changes)
    }
}
