//! IEC 60870-5-104 protocol adapter.
//!
//! This module provides the `Iec104Channel` adapter that integrates
//! `voltage_iec104` with igw's `Protocol` and `EventDrivenProtocol` traits.
//!
//! IEC 104 is an event-driven protocol - data is received via spontaneous
//! transmissions from the controlled station (RTU/substation).
//!
//! # Example
//!
//! ```rust,ignore
//! use igw::prelude::*;
//! use igw::protocols::iec104::{Iec104Channel, Iec104ChannelConfig};
//!
//! let config = Iec104ChannelConfig::new("192.168.1.100:2404")
//!     .with_common_address(1);
//!
//! let mut channel = Iec104Channel::new(config, store);
//! channel.connect().await?;
//! channel.start_data_transfer().await?;
//!
//! // Receive events via subscription
//! let mut rx = channel.subscribe();
//! while let Some(event) = rx.recv().await {
//!     match event {
//!         DataEvent::DataUpdate(batch) => { /* process data */ }
//!         _ => {}
//!     }
//! }
//! ```

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use chrono::{DateTime, TimeZone, Utc};
use tokio::sync::{mpsc, RwLock};
use voltage_iec104::{ClientConfig, Cp56Time2a, Iec104Client, Iec104Event};

use crate::core::data::{DataBatch, DataPoint, DataType, Value};
use crate::core::error::{GatewayError, Result};
use crate::core::point::PointConfig;
use crate::core::quality::Quality;
use crate::core::traits::{
    AdjustmentCommand, CommunicationMode, ConnectionState, ControlCommand, DataEvent,
    DataEventHandler, DataEventReceiver, DataEventSender, Diagnostics, EventDrivenProtocol,
    PollingConfig, Protocol, ProtocolCapabilities, ProtocolClient, ReadRequest, ReadResponse,
    WriteResult,
};

/// IEC 104 channel configuration.
#[derive(Debug, Clone)]
pub struct Iec104ChannelConfig {
    /// Target address (e.g., "192.168.1.100:2404")
    pub address: String,

    /// Common address of ASDU (station address)
    pub common_address: u16,

    /// Connection timeout
    pub connect_timeout: Duration,

    /// T1 timeout (send/receive APDU)
    pub t1_timeout: Duration,

    /// T2 timeout (no data acknowledgement)
    pub t2_timeout: Duration,

    /// T3 timeout (test frame)
    pub t3_timeout: Duration,

    /// Max unconfirmed I-frames (K parameter)
    pub k: u16,

    /// Latest ack threshold (W parameter)
    pub w: u16,

    /// Point configurations (IOA to point mapping)
    pub points: Vec<PointConfig>,

    /// IOA to point ID mapping (built from points)
    ioa_mapping: HashMap<u32, u32>,
}

impl Iec104ChannelConfig {
    /// Create a new configuration.
    pub fn new(address: impl Into<String>) -> Self {
        Self {
            address: address.into(),
            common_address: 1,
            connect_timeout: Duration::from_secs(10),
            t1_timeout: Duration::from_secs(15),
            t2_timeout: Duration::from_secs(10),
            t3_timeout: Duration::from_secs(20),
            k: 12,
            w: 8,
            points: Vec::new(),
            ioa_mapping: HashMap::new(),
        }
    }

    /// Set common address.
    pub fn with_common_address(mut self, addr: u16) -> Self {
        self.common_address = addr;
        self
    }

    /// Set connection timeout.
    pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
        self.connect_timeout = timeout;
        self
    }

    /// Set T1 timeout.
    pub fn with_t1_timeout(mut self, timeout: Duration) -> Self {
        self.t1_timeout = timeout;
        self
    }

    /// Set T2 timeout.
    pub fn with_t2_timeout(mut self, timeout: Duration) -> Self {
        self.t2_timeout = timeout;
        self
    }

    /// Set T3 timeout.
    pub fn with_t3_timeout(mut self, timeout: Duration) -> Self {
        self.t3_timeout = timeout;
        self
    }

    /// Add point configurations.
    pub fn with_points(mut self, points: Vec<PointConfig>) -> Self {
        // Build IOA mapping from point configs
        for point in &points {
            if let crate::core::point::ProtocolAddress::Iec104(addr) = &point.address {
                self.ioa_mapping.insert(addr.ioa, point.id);
            }
        }
        self.points = points;
        self
    }

    /// Build voltage_iec104 ClientConfig.
    fn to_client_config(&self) -> ClientConfig {
        let mut config = ClientConfig::new(&self.address)
            .connect_timeout(self.connect_timeout)
            .t1_timeout(self.t1_timeout)
            .t2_timeout(self.t2_timeout)
            .t3_timeout(self.t3_timeout);
        config.k = self.k;
        config.w = self.w;
        config
    }
}

/// IEC 104 channel adapter.
///
/// This struct wraps a `voltage_iec104::Iec104Client` and implements
/// igw's `Protocol`, `ProtocolClient`, and `EventDrivenProtocol` traits.
///
/// Note: This adapter follows the "protocol layer separated from storage" design.
/// The channel returns DataBatch via events; the service layer handles persistence.
pub struct Iec104Channel {
    config: Iec104ChannelConfig,
    client: Iec104Client,
    state: Arc<std::sync::RwLock<ConnectionState>>,
    diagnostics: Arc<RwLock<ChannelDiagnostics>>,
    event_tx: DataEventSender,
    #[allow(dead_code)] // Reserved for future use in subscribe()
    event_rx: Option<DataEventReceiver>,
    event_handler: Option<Arc<dyn DataEventHandler>>,
    poll_task: Option<tokio::task::JoinHandle<()>>,
    /// Point ID -> index lookup for O(1) access
    point_index: HashMap<u32, usize>,
}

#[derive(Debug, Default)]
struct ChannelDiagnostics {
    recv_count: u64,
    send_count: u64,
    error_count: u64,
    last_error: Option<String>,
    last_interrogation: Option<std::time::Instant>,
}

impl Iec104Channel {
    /// Create a new IEC 104 channel.
    pub fn new(config: Iec104ChannelConfig) -> Self {
        let client_config = config.to_client_config();
        let client = Iec104Client::new(client_config);
        let (event_tx, event_rx) = mpsc::channel(1024);

        // Build point ID -> index mapping for O(1) lookup
        let point_index: HashMap<u32, usize> = config
            .points
            .iter()
            .enumerate()
            .map(|(i, p)| (p.id, i))
            .collect();

        Self {
            config,
            client,
            state: Arc::new(std::sync::RwLock::new(ConnectionState::Disconnected)),
            diagnostics: Arc::new(RwLock::new(ChannelDiagnostics::default())),
            event_tx,
            event_rx: Some(event_rx),
            event_handler: None,
            poll_task: None,
            point_index,
        }
    }

    /// Set connection state.
    fn set_state(&self, state: ConnectionState) {
        if let Ok(mut s) = self.state.write() {
            *s = state;
        }
    }

    /// Get connection state.
    fn get_state(&self) -> ConnectionState {
        self.state
            .read()
            .map(|s| *s)
            .unwrap_or(ConnectionState::Error)
    }

    /// Start data transfer (STARTDT).
    pub async fn start_data_transfer(&mut self) -> Result<()> {
        self.client
            .start_dt()
            .await
            .map_err(|e| GatewayError::Protocol(e.to_string()))
    }

    /// Stop data transfer (STOPDT).
    pub async fn stop_data_transfer(&mut self) -> Result<()> {
        self.client
            .stop_dt()
            .await
            .map_err(|e| GatewayError::Protocol(e.to_string()))
    }

    /// Send general interrogation command.
    pub async fn general_interrogation(&mut self) -> Result<()> {
        self.client
            .general_interrogation(self.config.common_address)
            .await
            .map_err(|e| GatewayError::Protocol(e.to_string()))?;

        let mut diag = self.diagnostics.write().await;
        diag.last_interrogation = Some(std::time::Instant::now());
        Ok(())
    }

    /// Send counter interrogation command.
    ///
    /// Group 5 = request group 1 counter interrogation (general)
    pub async fn counter_interrogation(&mut self, group: u8) -> Result<()> {
        self.client
            .counter_interrogation(self.config.common_address, group)
            .await
            .map_err(|e| GatewayError::Protocol(e.to_string()))
    }

    /// Send clock synchronization command.
    pub async fn clock_sync(&mut self) -> Result<()> {
        let time = cp56time2a_now();
        self.client
            .clock_sync(self.config.common_address, time)
            .await
            .map_err(|e| GatewayError::Protocol(e.to_string()))
    }

    /// Poll for events (must be called periodically).
    pub async fn poll(&mut self) -> Result<()> {
        match self.client.poll().await {
            Ok(Some(event)) => {
                self.handle_iec104_event(event).await;
                Ok(())
            }
            Ok(None) => Ok(()),
            Err(e) => {
                self.record_error(&e.to_string()).await;
                Err(GatewayError::Protocol(e.to_string()))
            }
        }
    }

    /// Handle IEC 104 event.
    async fn handle_iec104_event(&self, event: Iec104Event) {
        match event {
            Iec104Event::Connected => {
                self.set_state(ConnectionState::Connected);
                let _ = self
                    .event_tx
                    .send(DataEvent::ConnectionChanged(ConnectionState::Connected))
                    .await;
            }
            Iec104Event::Disconnected => {
                self.set_state(ConnectionState::Disconnected);
                let _ = self
                    .event_tx
                    .send(DataEvent::ConnectionChanged(ConnectionState::Disconnected))
                    .await;
            }
            Iec104Event::DataTransferStarted => {
                // Data transfer is active
            }
            Iec104Event::DataTransferStopped => {
                // Data transfer stopped
            }
            Iec104Event::DataUpdate(points) => {
                let batch = self.convert_data_points(points).await;
                if !batch.is_empty() {
                    // Send event (service layer handles storage)
                    let _ = self.event_tx.send(DataEvent::DataUpdate(batch)).await;

                    // Update diagnostics
                    let mut diag = self.diagnostics.write().await;
                    diag.recv_count += 1;
                }
            }
            Iec104Event::AsduReceived(_asdu) => {
                // Raw ASDU - usually for command responses
            }
            Iec104Event::CommandConfirm { ioa, success } => {
                // Command confirmation
                let mut diag = self.diagnostics.write().await;
                if success {
                    diag.send_count += 1;
                } else {
                    diag.error_count += 1;
                    diag.last_error = Some(format!("Command failed for IOA {}", ioa));
                }
            }
            Iec104Event::InterrogationComplete { common_address: _ } => {
                // Interrogation finished
            }
            Iec104Event::Error(msg) => {
                self.record_error(&msg).await;
                let _ = self.event_tx.send(DataEvent::Error(msg)).await;
            }
        }
    }

    /// Convert IEC 104 data points to igw DataBatch.
    async fn convert_data_points(&self, points: Vec<voltage_iec104::DataPoint>) -> DataBatch {
        let mut batch = DataBatch::new();

        for point in points {
            // Look up point ID from IOA, or use IOA directly as fallback
            let point_id = self
                .config
                .ioa_mapping
                .get(&point.ioa)
                .copied()
                .unwrap_or(point.ioa);

            // Convert value
            let value = convert_iec104_value(&point.value);

            // Convert quality
            let quality = convert_iec104_quality(&point.quality);

            // Determine data type based on value
            let data_type = if point.value.is_boolean() {
                DataType::Signal
            } else {
                DataType::Telemetry
            };

            // Convert source timestamp
            let source_timestamp = point.timestamp.as_ref().and_then(cp56time2a_to_datetime);

            let dp = DataPoint {
                id: point_id,
                data_type,
                value,
                quality,
                timestamp: Utc::now(),
                source_timestamp,
            };

            batch.add(dp);
        }

        batch
    }

    /// Record an error.
    async fn record_error(&self, error: &str) {
        let mut diag = self.diagnostics.write().await;
        diag.error_count += 1;
        diag.last_error = Some(error.to_string());
    }

    /// Find point config by ID (O(1) lookup).
    fn find_point(&self, id: u32) -> Option<&PointConfig> {
        self.point_index
            .get(&id)
            .map(|&idx| &self.config.points[idx])
    }
}

impl ProtocolCapabilities for Iec104Channel {
    fn name(&self) -> &'static str {
        "IEC 60870-5-104"
    }

    fn supported_modes(&self) -> &[CommunicationMode] {
        &[CommunicationMode::EventDriven, CommunicationMode::Polling]
    }

    fn version(&self) -> &'static str {
        "1.0"
    }
}

impl Protocol for Iec104Channel {
    fn connection_state(&self) -> ConnectionState {
        self.get_state()
    }

    async fn read(&self, _request: ReadRequest) -> Result<ReadResponse> {
        // IEC 104 is event-driven, not polling-based
        // Return current values from store
        Err(GatewayError::Unsupported(
            "IEC 104 is event-driven; use subscribe() or general_interrogation()".into(),
        ))
    }

    async fn diagnostics(&self) -> Result<Diagnostics> {
        let state = self.get_state();
        let diag = self.diagnostics.read().await;

        Ok(Diagnostics {
            protocol: self.name().to_string(),
            connection_state: state,
            read_count: diag.recv_count,
            write_count: diag.send_count,
            error_count: diag.error_count,
            last_error: diag.last_error.clone(),
            extra: serde_json::json!({
                "address": self.config.address,
                "common_address": self.config.common_address,
                "points": self.config.points.len(),
                "last_interrogation": diag.last_interrogation.map(|t| t.elapsed().as_secs()),
            }),
        })
    }
}

impl ProtocolClient for Iec104Channel {
    async fn connect(&mut self) -> Result<()> {
        self.set_state(ConnectionState::Connecting);

        match self.client.connect().await {
            Ok(()) => {
                self.set_state(ConnectionState::Connected);
                Ok(())
            }
            Err(e) => {
                self.set_state(ConnectionState::Error);
                self.record_error(&e.to_string()).await;
                Err(GatewayError::Connection(e.to_string()))
            }
        }
    }

    async fn disconnect(&mut self) -> Result<()> {
        // Stop poll task if running
        if let Some(task) = self.poll_task.take() {
            task.abort();
        }

        // Stop data transfer first
        let _ = self.stop_data_transfer().await;

        // Disconnect
        match self.client.disconnect().await {
            Ok(()) => {
                self.set_state(ConnectionState::Disconnected);
                Ok(())
            }
            Err(e) => Err(GatewayError::Connection(e.to_string())),
        }
    }

    async fn poll_once(&mut self) -> Result<DataBatch> {
        // IEC 104 is event-driven, so poll_once fetches any pending events
        // from the underlying client and converts them to a DataBatch.
        let event = self.client.poll().await.map_err(|e| {
            self.diagnostics.blocking_write().error_count += 1;
            GatewayError::Protocol(e.to_string())
        })?;

        let mut batch = DataBatch::new();
        if let Some(voltage_iec104::Iec104Event::DataUpdate(points)) = event {
            let converted = self.convert_data_points(points).await;
            for point in converted.iter() {
                batch.add(point.clone());
            }
        }

        if !batch.is_empty() {
            let mut diag = self.diagnostics.write().await;
            diag.recv_count += 1;
        }

        Ok(batch)
    }

    async fn write_control(&mut self, commands: &[ControlCommand]) -> Result<WriteResult> {
        let mut success_count = 0;
        let mut failures = Vec::new();

        for cmd in commands {
            // Find point config
            let point = match self.find_point(cmd.id) {
                Some(p) => p,
                None => {
                    failures.push((cmd.id, "Point not found".into()));
                    continue;
                }
            };

            // Get IEC 104 address
            let iec_addr = match &point.address {
                crate::core::point::ProtocolAddress::Iec104(addr) => addr,
                _ => {
                    failures.push((cmd.id, "Invalid address type".into()));
                    continue;
                }
            };

            // Send single command
            let result = self
                .client
                .single_command(
                    self.config.common_address,
                    iec_addr.ioa,
                    cmd.value,
                    false, // not select
                )
                .await;

            match result {
                Ok(()) => success_count += 1,
                Err(e) => {
                    failures.push((cmd.id, e.to_string()));
                }
            }
        }

        {
            let mut diag = self.diagnostics.write().await;
            diag.send_count += success_count as u64;
        }

        Ok(WriteResult {
            success_count,
            failures,
        })
    }

    async fn write_adjustment(&mut self, adjustments: &[AdjustmentCommand]) -> Result<WriteResult> {
        let mut success_count = 0;
        let mut failures = Vec::new();

        for adj in adjustments {
            // Find point config
            let point = match self.find_point(adj.id) {
                Some(p) => p,
                None => {
                    failures.push((adj.id, "Point not found".into()));
                    continue;
                }
            };

            // Get IEC 104 address
            let iec_addr = match &point.address {
                crate::core::point::ProtocolAddress::Iec104(addr) => addr,
                _ => {
                    failures.push((adj.id, "Invalid address type".into()));
                    continue;
                }
            };

            // Apply reverse transform
            let raw_value = match point.transform.reverse_apply(adj.value) {
                Ok(v) => v as f32,
                Err(e) => {
                    failures.push((adj.id, e.to_string()));
                    continue;
                }
            };

            // Send setpoint command
            let result = self
                .client
                .setpoint_float(
                    self.config.common_address,
                    iec_addr.ioa,
                    raw_value,
                    false, // not select
                )
                .await;

            match result {
                Ok(()) => success_count += 1,
                Err(e) => {
                    failures.push((adj.id, e.to_string()));
                }
            }
        }

        {
            let mut diag = self.diagnostics.write().await;
            diag.send_count += success_count as u64;
        }

        Ok(WriteResult {
            success_count,
            failures,
        })
    }

    async fn start_polling(&mut self, config: PollingConfig) -> Result<()> {
        // For IEC 104, "polling" means starting a background task that calls poll()
        // and optionally sends periodic general interrogations

        let _interval = Duration::from_millis(config.interval_ms);

        // Start data transfer first
        self.start_data_transfer().await?;

        // Send initial general interrogation
        self.general_interrogation().await?;

        // TODO: Implement actual poll loop with proper channel cloning or task management
        // For now, we just indicate that polling has been configured.

        Ok(())
    }

    async fn stop_polling(&mut self) -> Result<()> {
        if let Some(task) = self.poll_task.take() {
            task.abort();
        }
        self.stop_data_transfer().await
    }
}

impl EventDrivenProtocol for Iec104Channel {
    fn subscribe(&self) -> DataEventReceiver {
        // Note: This is a stub implementation that creates a new channel.
        // In a real implementation, we'd need to properly manage subscriptions.
        // The event_rx stored in the struct should be used instead.
        let (_tx, rx) = mpsc::channel(1024);
        rx
    }

    fn set_event_handler(&mut self, handler: Arc<dyn DataEventHandler>) {
        self.event_handler = Some(handler);
    }
}

/// Convert IEC 104 DataValue to igw Value.
fn convert_iec104_value(value: &voltage_iec104::DataValue) -> Value {
    match value {
        voltage_iec104::DataValue::Single(v) => Value::Bool(*v),
        voltage_iec104::DataValue::Double(dp) => {
            use voltage_iec104::DoublePointValue;
            match dp {
                DoublePointValue::Off => Value::Bool(false),
                DoublePointValue::On => Value::Bool(true),
                DoublePointValue::Indeterminate | DoublePointValue::IndeterminateOrFaulty => {
                    Value::Null
                }
            }
        }
        voltage_iec104::DataValue::Normalized(v) => Value::Float(*v as f64),
        voltage_iec104::DataValue::Scaled(v) => Value::Integer(*v as i64),
        voltage_iec104::DataValue::Float(v) => Value::Float(*v as f64),
        voltage_iec104::DataValue::Counter(v) => Value::Integer(*v as i64),
        voltage_iec104::DataValue::Bitstring(v) => Value::Integer(*v as i64),
        voltage_iec104::DataValue::StepPosition(v) => Value::Integer(*v as i64),
        voltage_iec104::DataValue::BinaryCounter { value, .. } => Value::Integer(*value as i64),
    }
}

/// Convert Cp56Time2a to DateTime<Utc>.
fn cp56time2a_to_datetime(time: &Cp56Time2a) -> Option<DateTime<Utc>> {
    if time.invalid {
        return None;
    }

    // Year is stored as offset from 2000
    let year = 2000 + time.year as i32;
    let month = time.month as u32;
    let day = time.day as u32;
    let hour = time.hours as u32;
    let minute = time.minutes as u32;
    let second = (time.milliseconds / 1000) as u32;
    let millisecond = (time.milliseconds % 1000) as u32;

    Utc.with_ymd_and_hms(year, month, day, hour, minute, second)
        .single()
        .map(|dt| dt + chrono::Duration::milliseconds(millisecond as i64))
}

/// Create Cp56Time2a from current time.
fn cp56time2a_now() -> Cp56Time2a {
    use chrono::Datelike;
    use chrono::Timelike;

    let now = Utc::now();

    Cp56Time2a {
        milliseconds: now.second() as u16 * 1000 + now.timestamp_subsec_millis() as u16,
        minutes: now.minute() as u8,
        hours: now.hour() as u8,
        day: now.day() as u8,
        day_of_week: now.weekday().num_days_from_monday() as u8 + 1, // 1=Monday
        month: now.month() as u8,
        year: ((now.year() as u16).saturating_sub(2000) & 0x7F) as u8,
        invalid: false,
        summer_time: false,
    }
}

/// Convert IEC 104 Quality to igw Quality.
fn convert_iec104_quality(quality: &voltage_iec104::Quality) -> Quality {
    if quality.is_good() {
        Quality::Good
    } else if quality.invalid {
        Quality::Invalid
    } else {
        Quality::Uncertain
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_iec104_channel_config() {
        let config = Iec104ChannelConfig::new("127.0.0.1:2404")
            .with_common_address(1)
            .with_connect_timeout(Duration::from_secs(10));

        assert_eq!(config.address, "127.0.0.1:2404");
        assert_eq!(config.common_address, 1);
        assert_eq!(config.connect_timeout, Duration::from_secs(10));
    }

    #[test]
    fn test_iec104_channel_capabilities() {
        let config = Iec104ChannelConfig::new("127.0.0.1:2404");
        let channel = Iec104Channel::new(config);

        assert_eq!(channel.name(), "IEC 60870-5-104");
        assert!(channel
            .supported_modes()
            .contains(&CommunicationMode::EventDriven));
    }

    #[test]
    fn test_convert_iec104_value() {
        assert_eq!(
            convert_iec104_value(&voltage_iec104::DataValue::Single(true)),
            Value::Bool(true)
        );
        assert_eq!(
            convert_iec104_value(&voltage_iec104::DataValue::Float(23.5)),
            Value::Float(23.5)
        );
        assert_eq!(
            convert_iec104_value(&voltage_iec104::DataValue::Scaled(100)),
            Value::Integer(100)
        );
    }

    #[test]
    fn test_cp56time2a_conversion() {
        let time = Cp56Time2a {
            milliseconds: 30500, // 30 seconds, 500 ms
            minutes: 15,
            hours: 10,
            day: 25,
            day_of_week: 3,
            month: 12,
            year: 24, // 2024
            invalid: false,
            summer_time: false,
        };

        let dt = cp56time2a_to_datetime(&time).unwrap();
        assert_eq!(
            dt.format("%Y-%m-%d %H:%M:%S").to_string(),
            "2024-12-25 10:15:30"
        );
    }
}
