use std::fs;
use std::path::Path;
use std::time::Duration;

use serde::Deserialize;
use tidaldb::schema::{DecaySpec, EntityKind, Schema, SchemaBuilder, TextFieldType, Window};

use crate::error::{Result, ServerError};

const DEFAULT_SCHEMA_YAML: &str = include_str!("../config/default-schema.yaml");

pub fn load_schema(path: Option<&Path>) -> Result<Schema> {
    let raw = read_config(path, DEFAULT_SCHEMA_YAML)?;
    let spec: SchemaSpec = serde_yaml::from_str(&raw)
        .map_err(|e| ServerError::SchemaConfig(format!("parse schema yaml: {e}")))?;

    if spec.signals.is_empty() {
        return Err(ServerError::SchemaConfig(
            "at least one signal must be defined".into(),
        ));
    }

    let mut builder = SchemaBuilder::new();
    for signal in spec.signals {
        let mut sig = builder.signal(
            &signal.name,
            parse_entity_kind(&signal.entity)?,
            signal.decay.to_decay_spec()?,
        );
        if let Some(windows) = signal.windows {
            let parsed: Result<Vec<Window>> = windows.iter().map(|w| parse_window(w)).collect();
            sig = sig.windows(&parsed?);
        }
        if let Some(velocity) = signal.velocity {
            sig = sig.velocity(velocity);
        }
        let _ = sig.add();
    }

    if let Some(text_fields) = spec.text_fields {
        for field in text_fields {
            builder.text_field(&field.name, parse_text_field_type(&field.kind)?);
        }
    }

    if let Some(embeddings) = spec.embedding_slots {
        for slot in embeddings {
            builder.embedding_slot(
                &slot.name,
                parse_entity_kind(&slot.entity)?,
                slot.dimensions,
            );
        }
    }

    builder.build().map_err(ServerError::SchemaBuild)
}

fn read_config(path: Option<&Path>, fallback: &str) -> Result<String> {
    match path {
        Some(p) => fs::read_to_string(p).map_err(|e| ServerError::io(p, e)),
        None => Ok(fallback.to_string()),
    }
}

#[derive(Debug, Deserialize)]
struct SchemaSpec {
    signals: Vec<SignalSpec>,
    #[serde(default)]
    text_fields: Option<Vec<TextFieldSpec>>,
    #[serde(default)]
    embedding_slots: Option<Vec<EmbeddingSpec>>,
}

#[derive(Debug, Deserialize)]
struct SignalSpec {
    name: String,
    entity: String,
    decay: DecaySpecConfig,
    #[serde(default)]
    windows: Option<Vec<String>>,
    #[serde(default)]
    velocity: Option<bool>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct DecaySpecConfig {
    #[serde(default)]
    exponential: Option<ExponentialDecay>,
    #[serde(default)]
    linear: Option<LinearDecay>,
    #[serde(default)]
    permanent: Option<bool>,
}

impl DecaySpecConfig {
    fn to_decay_spec(&self) -> Result<DecaySpec> {
        if let Some(exp) = &self.exponential {
            return Ok(DecaySpec::Exponential {
                half_life: Duration::from_secs_f64(exp.half_life_seconds),
            });
        }
        if let Some(linear) = &self.linear {
            return Ok(DecaySpec::Linear {
                lifetime: Duration::from_secs_f64(linear.lifetime_seconds),
            });
        }
        if self.permanent.unwrap_or(false) {
            return Ok(DecaySpec::Permanent);
        }
        Err(ServerError::SchemaConfig(
            "decay must specify exponential, linear, or permanent".into(),
        ))
    }
}

#[derive(Debug, Deserialize)]
struct ExponentialDecay {
    half_life_seconds: f64,
}

#[derive(Debug, Deserialize)]
struct LinearDecay {
    lifetime_seconds: f64,
}

#[derive(Debug, Deserialize)]
struct TextFieldSpec {
    name: String,
    kind: String,
}

#[derive(Debug, Deserialize)]
struct EmbeddingSpec {
    name: String,
    entity: String,
    dimensions: usize,
}

fn parse_entity_kind(input: &str) -> Result<EntityKind> {
    match input.trim().to_lowercase().as_str() {
        "item" | "items" => Ok(EntityKind::Item),
        "user" | "users" => Ok(EntityKind::User),
        "creator" | "creators" => Ok(EntityKind::Creator),
        other => Err(ServerError::SchemaConfig(format!(
            "unknown entity kind '{other}'"
        ))),
    }
}

fn parse_window(input: &str) -> Result<Window> {
    match input.trim().to_lowercase().as_str() {
        "one_hour" | "1h" => Ok(Window::OneHour),
        "twenty_four_hours" | "24h" => Ok(Window::TwentyFourHours),
        "seven_days" | "7d" => Ok(Window::SevenDays),
        "thirty_days" | "30d" => Ok(Window::ThirtyDays),
        "all_time" | "alltime" => Ok(Window::AllTime),
        other => Err(ServerError::SchemaConfig(format!(
            "unknown window '{other}'"
        ))),
    }
}

fn parse_text_field_type(input: &str) -> Result<TextFieldType> {
    match input.trim().to_lowercase().as_str() {
        "text" => Ok(TextFieldType::Text),
        "keyword" => Ok(TextFieldType::Keyword),
        other => Err(ServerError::SchemaConfig(format!(
            "unknown text field type '{other}'"
        ))),
    }
}
