use std::collections::BTreeSet;

use serde::Serialize;
use tauri::{AppHandle, Emitter, Runtime};

use super::{CustomConceptMapping, MappingScope, TargetField};

pub const MAPPINGS_UPDATED_EVENT: &str = "mappings_updated";

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum MappingChangeKind {
    Created,
    Updated,
    Deleted,
    Toggled,
    Imported,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MappingRefreshScope {
    pub full_refresh: bool,
    pub affected_scopes: Vec<MappingScope>,
    pub affected_targets: Vec<TargetField>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MappingsUpdatedEvent {
    pub change_kind: MappingChangeKind,
    pub refresh_scope: MappingRefreshScope,
}

pub trait MappingsEventEmitter: Send + Sync {
    fn emit_updated(&self, payload: &MappingsUpdatedEvent) -> Result<(), String>;
}

#[cfg(test)]
pub struct NoopMappingsEventEmitter;

#[cfg(test)]
impl MappingsEventEmitter for NoopMappingsEventEmitter {
    fn emit_updated(&self, _payload: &MappingsUpdatedEvent) -> Result<(), String> {
        Ok(())
    }
}

pub struct TauriMappingsEventEmitter<R: Runtime> {
    app_handle: AppHandle<R>,
}

impl<R: Runtime> TauriMappingsEventEmitter<R> {
    pub fn new(app_handle: &AppHandle<R>) -> Self {
        Self {
            app_handle: app_handle.clone(),
        }
    }
}

impl<R: Runtime> MappingsEventEmitter for TauriMappingsEventEmitter<R> {
    fn emit_updated(&self, payload: &MappingsUpdatedEvent) -> Result<(), String> {
        emit_mappings_updated(&self.app_handle, payload).map_err(|error| error.to_string())
    }
}

impl MappingsUpdatedEvent {
    pub fn for_mapping(change_kind: MappingChangeKind, mapping: &CustomConceptMapping) -> Self {
        Self::from_parts(
            change_kind,
            false,
            [mapping.scope.clone()],
            [mapping.target_field],
        )
    }

    pub fn for_updated(previous: &CustomConceptMapping, current: &CustomConceptMapping) -> Self {
        Self::from_parts(
            MappingChangeKind::Updated,
            false,
            [previous.scope.clone(), current.scope.clone()],
            [previous.target_field, current.target_field],
        )
    }

    pub fn for_imported(mappings: &[CustomConceptMapping]) -> Self {
        Self::from_parts(
            MappingChangeKind::Imported,
            true,
            mappings
                .iter()
                .map(|mapping| mapping.scope.clone())
                .collect::<Vec<_>>(),
            mappings
                .iter()
                .map(|mapping| mapping.target_field)
                .collect::<Vec<_>>(),
        )
    }

    fn from_parts<I, J>(
        change_kind: MappingChangeKind,
        full_refresh: bool,
        affected_scopes: I,
        affected_targets: J,
    ) -> Self
    where
        I: IntoIterator<Item = MappingScope>,
        J: IntoIterator<Item = TargetField>,
    {
        let mut unique_scopes = BTreeSet::new();
        let mut scopes = Vec::new();
        for scope in affected_scopes {
            if unique_scopes.insert(scope_key(&scope)) {
                scopes.push(scope);
            }
        }

        let mut targets = Vec::new();
        for target in affected_targets {
            if !targets.contains(&target) {
                targets.push(target);
            }
        }

        Self {
            change_kind,
            refresh_scope: MappingRefreshScope {
                full_refresh,
                affected_scopes: scopes,
                affected_targets: targets,
            },
        }
    }
}

pub fn emit_mappings_updated<R: Runtime>(
    app_handle: &AppHandle<R>,
    payload: &MappingsUpdatedEvent,
) -> Result<(), tauri::Error> {
    app_handle.emit(MAPPINGS_UPDATED_EVENT, payload)
}

fn scope_key(scope: &MappingScope) -> (Option<String>, Option<String>, Option<String>) {
    (
        scope.ticker.clone(),
        scope.period_start.clone(),
        scope.period_end.clone(),
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mappings::{CustomConcept, MappingScope, TargetField};
    use crate::terminal::sec_edgar::UnitFamily;

    fn sample_mapping(
        target_field: TargetField,
        ticker: Option<&str>,
        period_start: Option<&str>,
        period_end: Option<&str>,
    ) -> CustomConceptMapping {
        CustomConceptMapping::test_mapping(
            target_field,
            CustomConcept {
                taxonomy: "us-gaap".to_string(),
                concept: "RevenueFromContractWithCustomerExcludingAssessedTax".to_string(),
                unit_family: UnitFamily::Currency,
            },
            MappingScope {
                ticker: ticker.map(str::to_string),
                period_start: period_start.map(str::to_string),
                period_end: period_end.map(str::to_string),
            },
            None,
        )
    }

    #[test]
    fn created_event_uses_mapping_scope_and_target() {
        let mapping = sample_mapping(
            TargetField::TotalRevenues,
            Some("AAPL"),
            Some("2024-01-01"),
            None,
        );

        let event = MappingsUpdatedEvent::for_mapping(MappingChangeKind::Created, &mapping);

        assert_eq!(event.change_kind, MappingChangeKind::Created);
        assert!(!event.refresh_scope.full_refresh);
        assert_eq!(event.refresh_scope.affected_scopes, vec![mapping.scope]);
        assert_eq!(
            event.refresh_scope.affected_targets,
            vec![TargetField::TotalRevenues]
        );
    }

    #[test]
    fn updated_event_deduplicates_old_and_new_scopes_and_targets() {
        let previous = sample_mapping(TargetField::GrossProfit, Some("AAPL"), None, None);
        let current = sample_mapping(
            TargetField::GrossProfit,
            Some("AAPL"),
            Some("2024-01-01"),
            None,
        );

        let event = MappingsUpdatedEvent::for_updated(&previous, &current);

        assert_eq!(event.change_kind, MappingChangeKind::Updated);
        assert_eq!(event.refresh_scope.affected_scopes.len(), 2);
        assert_eq!(
            event.refresh_scope.affected_targets,
            vec![TargetField::GrossProfit]
        );
    }

    #[test]
    fn imported_event_sets_full_refresh_and_deduplicates_fields() {
        let mappings = vec![
            sample_mapping(TargetField::TotalRevenues, Some("AAPL"), None, None),
            sample_mapping(TargetField::TotalRevenues, Some("AAPL"), None, None),
            sample_mapping(TargetField::NetIncome, Some("MSFT"), None, None),
        ];

        let event = MappingsUpdatedEvent::for_imported(&mappings);

        assert_eq!(event.change_kind, MappingChangeKind::Imported);
        assert!(event.refresh_scope.full_refresh);
        assert_eq!(event.refresh_scope.affected_scopes.len(), 2);
        assert_eq!(
            event.refresh_scope.affected_targets,
            vec![TargetField::TotalRevenues, TargetField::NetIncome]
        );
    }
}
