//! In-memory mock for OVN interactions (used in tests and CI)

use std::collections::HashMap;

use prismnet_types::{DhcpOptions, PortId, SecurityGroupId, SecurityGroupRule, VpcId};

#[derive(Debug, Clone)]
pub struct MockLogicalSwitch {
    pub cidr: String,
}

#[derive(Debug, Clone)]
pub struct MockLogicalSwitchPort {
    pub logical_switch: VpcId,
    pub mac: String,
    pub ip: String,
}

#[derive(Debug, Clone)]
pub struct MockAcl {
    pub security_group: SecurityGroupId,
    pub rule: SecurityGroupRule,
    pub logical_switch: VpcId,
    pub match_expr: String,
}

#[derive(Debug, Clone)]
pub struct MockDhcpOptions {
    pub cidr: String,
    pub options: DhcpOptions,
}

#[derive(Debug, Clone)]
pub struct MockRouter {
    pub id: String,
    pub name: String,
    pub ports: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct MockRouterPort {
    pub id: String,
    pub router_id: String,
    pub switch_id: VpcId,
    pub mac: String,
    pub cidr: String,
}

#[derive(Debug, Clone)]
pub struct MockSnatRule {
    pub router_id: String,
    pub external_ip: String,
    pub logical_ip_cidr: String,
}

/// Mock OVN state tracker
#[derive(Debug, Default, Clone)]
pub struct MockOvnState {
    pub logical_switches: HashMap<VpcId, MockLogicalSwitch>,
    pub logical_ports: HashMap<PortId, MockLogicalSwitchPort>,
    pub acls: HashMap<String, MockAcl>,
    pub dhcp_options: HashMap<String, MockDhcpOptions>,
    pub port_dhcp_bindings: HashMap<String, String>,
    pub routers: HashMap<String, MockRouter>,
    pub router_ports: HashMap<String, MockRouterPort>,
    pub snat_rules: Vec<MockSnatRule>,
}

impl MockOvnState {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn create_logical_switch(&mut self, vpc_id: VpcId, cidr: String) {
        self.logical_switches
            .insert(vpc_id, MockLogicalSwitch { cidr });
    }

    pub fn delete_logical_switch(&mut self, vpc_id: &VpcId) {
        self.logical_switches.remove(vpc_id);
        self.logical_ports
            .retain(|_, port| &port.logical_switch != vpc_id);
        self.acls.retain(|_, acl| &acl.logical_switch != vpc_id);
    }

    pub fn create_logical_switch_port(
        &mut self,
        port_id: PortId,
        logical_switch: VpcId,
        mac: String,
        ip: String,
    ) {
        self.logical_ports.insert(
            port_id,
            MockLogicalSwitchPort {
                logical_switch,
                mac,
                ip,
            },
        );
    }

    pub fn delete_logical_switch_port(&mut self, port_id: &PortId) {
        self.logical_ports.remove(port_id);
    }

    pub fn create_acl(
        &mut self,
        key: String,
        security_group: SecurityGroupId,
        rule: SecurityGroupRule,
        logical_switch: VpcId,
        match_expr: String,
    ) {
        self.acls.insert(
            key,
            MockAcl {
                security_group,
                rule,
                logical_switch,
                match_expr,
            },
        );
    }

    pub fn delete_acl(&mut self, key: &str) {
        self.acls.remove(key);
    }

    // Convenience checks for tests
    pub fn has_logical_switch(&self, vpc_id: &VpcId) -> bool {
        self.logical_switches.contains_key(vpc_id)
    }

    pub fn port_attached(&self, port_id: &PortId) -> bool {
        self.logical_ports.contains_key(port_id)
    }

    pub fn acl_exists(&self, key: &str) -> bool {
        self.acls.contains_key(key)
    }

    pub fn create_dhcp_options(&mut self, cidr: String, options: DhcpOptions) -> String {
        let uuid = format!("dhcp-{}", uuid::Uuid::new_v4());
        self.dhcp_options.insert(
            uuid.clone(),
            MockDhcpOptions {
                cidr,
                options,
            },
        );
        uuid
    }

    pub fn delete_dhcp_options(&mut self, uuid: &str) {
        self.dhcp_options.remove(uuid);
    }

    pub fn set_lsp_dhcp_options(&mut self, lsp_name: String, dhcp_uuid: String) {
        self.port_dhcp_bindings.insert(lsp_name, dhcp_uuid);
    }

    pub fn dhcp_options_exists(&self, uuid: &str) -> bool {
        self.dhcp_options.contains_key(uuid)
    }

    pub fn port_has_dhcp(&self, lsp_name: &str) -> bool {
        self.port_dhcp_bindings.contains_key(lsp_name)
    }

    /// Get ACL match expression for testing
    pub fn get_acl_match(&self, key: &str) -> Option<&str> {
        self.acls.get(key).map(|acl| acl.match_expr.as_str())
    }

    // Router management
    pub fn create_router(&mut self, name: String) -> String {
        let id = format!("router-{}", uuid::Uuid::new_v4());
        self.routers.insert(
            id.clone(),
            MockRouter {
                id: id.clone(),
                name,
                ports: Vec::new(),
            },
        );
        id
    }

    pub fn delete_router(&mut self, router_id: &str) {
        self.routers.remove(router_id);
        // Clean up associated router ports
        self.router_ports.retain(|_, port| port.router_id != router_id);
        // Clean up associated SNAT rules
        self.snat_rules.retain(|rule| rule.router_id != router_id);
    }

    pub fn add_router_port(
        &mut self,
        router_id: String,
        switch_id: VpcId,
        cidr: String,
        mac: String,
    ) -> String {
        let port_id = format!("rtr-port-{}", uuid::Uuid::new_v4());

        // Add port to router's port list
        if let Some(router) = self.routers.get_mut(&router_id) {
            router.ports.push(port_id.clone());
        }

        self.router_ports.insert(
            port_id.clone(),
            MockRouterPort {
                id: port_id.clone(),
                router_id,
                switch_id,
                mac,
                cidr,
            },
        );
        port_id
    }

    pub fn configure_snat(
        &mut self,
        router_id: String,
        external_ip: String,
        logical_ip_cidr: String,
    ) {
        self.snat_rules.push(MockSnatRule {
            router_id,
            external_ip,
            logical_ip_cidr,
        });
    }

    // Convenience checks for tests
    pub fn router_exists(&self, router_id: &str) -> bool {
        self.routers.contains_key(router_id)
    }

    pub fn router_port_exists(&self, port_id: &str) -> bool {
        self.router_ports.contains_key(port_id)
    }

    pub fn snat_rule_exists(&self, router_id: &str, external_ip: &str) -> bool {
        self.snat_rules.iter().any(|rule| {
            rule.router_id == router_id && rule.external_ip == external_ip
        })
    }

    pub fn get_router_port_count(&self, router_id: &str) -> usize {
        self.router_ports
            .values()
            .filter(|port| port.router_id == router_id)
            .count()
    }
}
