use std::sync::Arc;

use crate::core::response::engine::SoarEngine;
use crate::domain::common::error::Error;
use crate::domain::response::error::SoarError;
use crate::domain::response::playbook_data::{
    ActiveBlockView, CreatePlaybookInput, ExecutionView, PlaybookView, UpdatePlaybookInput,
};
use crate::interface::port::access_control::AccessControlPort;
use crate::interface::port::app_repo::AppRepo;
use crate::utils::ip_address::ip_version_from_str;

/// Domain service for SOAR playbook CRUD operations.
/// Coordinates DB reads/writes, SOAR engine cache refresh, and eBPF unblock.
pub struct PlaybookService {
    db: Arc<dyn AppRepo>,
    soar_engine: Arc<SoarEngine>,
    access_control: Arc<dyn AccessControlPort>,
}

impl PlaybookService {
    pub fn new(db: Arc<dyn AppRepo>, soar_engine: Arc<SoarEngine>, access_control: Arc<dyn AccessControlPort>) -> Self {
        Self {
            db,
            soar_engine,
            access_control,
        }
    }

    pub fn list_playbooks(&self) -> Result<Vec<PlaybookView>, Error> {
        self.db.list_playbooks()
    }

    pub fn create_playbook(&self, input: &CreatePlaybookInput) -> Result<i64, Error> {
        // Single atomic insert (playbook + actions + conditions).
        let actions: Vec<(i64, String, String)> = input
            .actions
            .iter()
            .enumerate()
            .map(|(i, (ty, params))| ((i + 1) as i64, ty.clone(), params.clone()))
            .collect();
        let conditions: Vec<(String, String, String, Option<String>)> = input
            .conditions
            .iter()
            .map(|c| {
                (
                    c.condition_type.clone(),
                    c.operator.clone(),
                    c.value.clone(),
                    c.value2.clone(),
                )
            })
            .collect();
        let playbook_id = self.db.insert_playbook_atomic(input, &actions, &conditions)?;
        self.soar_engine.reload_cache()?;
        Ok(playbook_id)
    }

    pub fn update_playbook(&self, id: i64, input: &CreatePlaybookInput) -> Result<bool, Error> {
        let row = UpdatePlaybookInput {
            name: input.name.clone(),
            trigger_event: input.trigger_event.clone(),
            condition_threshold: input.condition_threshold,
            condition_count: input.condition_count,
            condition_window_secs: input.condition_window_secs,
            cooldown_secs: input.cooldown_secs,
        };
        // Single atomic update (playbook metadata + replace actions/conditions).
        let actions: Vec<(i64, String, String)> = input
            .actions
            .iter()
            .enumerate()
            .map(|(i, (ty, params))| ((i + 1) as i64, ty.clone(), params.clone()))
            .collect();
        let conditions: Vec<(String, String, String, Option<String>)> = input
            .conditions
            .iter()
            .map(|c| {
                (
                    c.condition_type.clone(),
                    c.operator.clone(),
                    c.value.clone(),
                    c.value2.clone(),
                )
            })
            .collect();
        let updated = self.db.update_playbook_atomic(id, &row, &actions, &conditions)?;
        if !updated {
            return Ok(false);
        }
        self.soar_engine.reload_cache()?;
        Ok(true)
    }

    pub fn toggle_playbook(&self, id: i64, enabled: bool) -> Result<bool, Error> {
        let updated = self.db.update_playbook_enabled(id, enabled)?;
        if updated {
            self.soar_engine.reload_cache()?;
        }
        Ok(updated)
    }

    pub fn delete_playbook(&self, id: i64) -> Result<bool, Error> {
        let deleted = self.db.delete_playbook(id)?;
        if deleted {
            self.soar_engine.reload_cache()?;
        }
        Ok(deleted)
    }

    pub fn list_active_blocks(&self) -> Result<Vec<ActiveBlockView>, Error> {
        self.db.list_active_soar_blocks()
    }

    /// Manually unblock an IP: remove from eBPF, atomically clear both DB
    /// tables via `DbAdminRepo::commit_soar_unblock_to_db` (tx-3), decrement
    /// counter.
    pub async fn manual_unblock(&self, id: i64) -> Result<(), Error> {
        // Look up the block to get source_ip
        let block = self
            .db
            .find_soar_block_by_id(id)?
            .ok_or_else(|| SoarError::UnblockRuleNotFound(id))?;
        let source_ip = &block.source_ip;

        // Remove from eBPF ACL
        self.access_control.unblock_ip(source_ip)?;

        // Atomically drop acl_rules entry AND mark soar_block_rules
        // unblocked in one transaction.
        let ip_version = ip_version_from_str(source_ip);
        self.db.commit_soar_unblock_to_db(id, ip_version, source_ip)?;

        // Decrement active block counter
        self.soar_engine.decrement_block_count();

        Ok(())
    }

    pub fn list_executions(&self, limit: i64) -> Result<Vec<ExecutionView>, Error> {
        self.db.list_soar_executions(limit)
    }

    pub fn list_whitelist(&self) -> Result<Vec<String>, Error> {
        self.db.list_admin_whitelist()
    }

    pub fn add_whitelist(&self, ip: &str) -> Result<(), Error> {
        self.db.insert_admin_whitelist(ip)?;
        self.soar_engine.reload_cache()?;
        Ok(())
    }

    pub fn remove_whitelist(&self, ip: &str) -> Result<(), Error> {
        self.db.delete_admin_whitelist(ip)?;
        self.soar_engine.reload_cache()?;
        Ok(())
    }
}
