use crate::cube_bridge::driver_tools::DriverTools;
use crate::cube_bridge::sql_templates_render::SqlTemplatesRender;
use crate::test_fixtures::cube_bridge::MockSqlTemplatesRender;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use cubenativeutils::CubeError;
use std::any::Any;
use std::rc::Rc;

/// Mock implementation of DriverTools for testing
///
/// This mock provides implementations based on PostgresQuery.ts and BaseQuery.js
/// from packages/cubejs-schema-compiler/src/adapter/
///
#[derive(Clone)]
pub struct MockDriverTools {
    timezone: String,
    timestamp_precision: u32,
    sql_templates: Rc<MockSqlTemplatesRender>,
    visible_in_db_time_zone: bool,
    /// When set, dialect-specific native methods mirror CubeStoreQuery
    /// instead of the Postgres defaults (used for external pre-aggregations).
    cubestore: bool,
    /// Mirrors the JS `shouldReuseParams` dialect flag: `true` for the Postgres
    /// defaults (`$1` addresses its value), `false` for positional `?`.
    should_reuse_params: bool,
}

impl MockDriverTools {
    pub fn new() -> Self {
        Self {
            timezone: "UTC".to_string(),
            timestamp_precision: 3,
            sql_templates: Rc::new(MockSqlTemplatesRender::default_templates()),
            visible_in_db_time_zone: false,
            cubestore: false,
            should_reuse_params: true,
        }
    }

    #[allow(dead_code)]
    pub fn with_timezone(timezone: String) -> Self {
        Self {
            timezone,
            timestamp_precision: 3,
            sql_templates: Rc::new(MockSqlTemplatesRender::default_templates()),
            visible_in_db_time_zone: false,
            cubestore: false,
            should_reuse_params: true,
        }
    }

    #[allow(dead_code)]
    pub fn with_sql_templates(sql_templates: MockSqlTemplatesRender) -> Self {
        Self {
            timezone: "UTC".to_string(),
            timestamp_precision: 3,
            sql_templates: Rc::new(sql_templates),
            visible_in_db_time_zone: false,
            cubestore: false,
            should_reuse_params: true,
        }
    }

    pub fn with_sql_templates_and_timezone(
        sql_templates: MockSqlTemplatesRender,
        timezone: String,
    ) -> Self {
        Self {
            timezone,
            timestamp_precision: 3,
            sql_templates: Rc::new(sql_templates),
            visible_in_db_time_zone: false,
            cubestore: false,
            should_reuse_params: true,
        }
    }

    /// Renders params as positional `?`, so a value cannot be shared between
    /// placeholders.
    #[allow(dead_code)]
    pub fn without_params_reuse(mut self) -> Self {
        self.should_reuse_params = false;
        self
    }

    #[allow(dead_code)]
    pub fn with_visible_in_db_time_zone(mut self) -> Self {
        self.visible_in_db_time_zone = true;
        self
    }

    /// Switches dialect-specific native methods to their CubeStore form,
    /// mirroring CubeStoreQuery.ts (used for external pre-aggregations).
    pub fn with_cubestore_dialect(mut self) -> Self {
        self.cubestore = true;
        self
    }

    /// Mirrors CubeStoreQuery.formatInterval: a `"<n> <unit>"` interval
    /// (possibly multi-unit) rendered in CubeStore (DataFusion) form, e.g.
    /// `6 month` → `'6 MONTH'`.
    fn format_interval(interval: &str) -> String {
        let mut tokens = interval.split_whitespace();
        let mut parts: Vec<String> = Vec::new();
        while let (Some(num), Some(unit)) = (tokens.next(), tokens.next()) {
            let unit = unit.trim_end_matches('s').to_uppercase();
            parts.push(format!("{} {}", num, unit));
        }
        format!("'{}'", parts.join(" "))
    }
}

impl Default for MockDriverTools {
    fn default() -> Self {
        Self::new()
    }
}

impl DriverTools for MockDriverTools {
    fn as_any(self: Rc<Self>) -> Rc<dyn Any> {
        self
    }

    fn convert_tz(&self, field: String) -> Result<String, CubeError> {
        if self.cubestore {
            // CubeStoreQuery.convertTz; sessions are pinned to UTC here.
            return Ok(format!("CONVERT_TZ({}, '+00:00')", field));
        }
        Ok(format!(
            "({}::timestamptz AT TIME ZONE '{}')",
            field, self.timezone
        ))
    }

    fn time_grouped_column(
        &self,
        granularity: String,
        dimension: String,
    ) -> Result<String, CubeError> {
        // Map granularity to Postgres interval (from PostgresQuery.ts:4-13)
        let interval = match granularity.as_str() {
            "day" => "day",
            "week" => "week",
            "hour" => "hour",
            "minute" => "minute",
            "second" => "second",
            "month" => "month",
            "quarter" => "quarter",
            "year" => "year",
            _ => {
                return Err(CubeError::user(format!(
                    "Unsupported granularity: {}",
                    granularity
                )))
            }
        };

        Ok(format!("date_trunc('{}', {})", interval, dimension))
    }

    fn sql_templates(&self) -> Result<Rc<dyn SqlTemplatesRender>, CubeError> {
        Ok(self.sql_templates.clone())
    }

    fn timestamp_precision(&self) -> Result<u32, CubeError> {
        Ok(self.timestamp_precision)
    }

    fn time_stamp_cast(&self, field: String) -> Result<String, CubeError> {
        if self.cubestore {
            // CubeStoreQuery.timeStampCast (DataFusion has no `::timestamptz`).
            return Ok(format!("CAST({} as TIMESTAMP)", field));
        }
        Ok(format!("{}::timestamptz", field))
    }

    fn date_time_cast(&self, field: String) -> Result<String, CubeError> {
        if self.cubestore {
            // CubeStoreQuery.dateTimeCast.
            return Ok(format!("to_timestamp({})", field));
        }
        Ok(format!("{}::timestamp", field))
    }

    fn in_db_time_zone(&self, date: String) -> Result<String, CubeError> {
        if self.timezone == "UTC" {
            return Ok(date);
        }
        let tz: chrono_tz::Tz = self
            .timezone
            .parse()
            .map_err(|e| CubeError::user(format!("Unknown timezone {}: {}", self.timezone, e)))?;

        let precision = self.timestamp_precision;
        let fmt = if precision == 3 {
            "%Y-%m-%dT%H:%M:%S%.3f"
        } else {
            "%Y-%m-%dT%H:%M:%S%.6f"
        };

        let naive = NaiveDateTime::parse_from_str(&date, fmt)
            .map_err(|e| CubeError::user(format!("Cannot parse timestamp '{}': {}", date, e)))?;

        let local_dt = tz
            .from_local_datetime(&naive)
            .single()
            .ok_or_else(|| CubeError::user(format!("Ambiguous local time: {}", date)))?;

        let utc_dt = local_dt.with_timezone(&chrono::Utc);
        let result = if precision == 3 {
            utc_dt.format("%Y-%m-%dT%H:%M:%S%.3f").to_string()
        } else {
            utc_dt.format("%Y-%m-%dT%H:%M:%S%.6f").to_string()
        };
        Ok(result)
    }

    fn get_allocated_params(&self) -> Result<Vec<String>, CubeError> {
        Ok(Vec::new())
    }

    fn should_reuse_params(&self) -> Result<bool, CubeError> {
        Ok(self.should_reuse_params)
    }

    fn subtract_interval(&self, date: String, interval: String) -> Result<String, CubeError> {
        let interval_str = self.interval_string(interval)?;
        Ok(format!("{} - interval {}", date, interval_str))
    }

    fn add_interval(&self, date: String, interval: String) -> Result<String, CubeError> {
        let interval_str = self.interval_string(interval)?;
        Ok(format!("{} + interval {}", date, interval_str))
    }

    fn interval_string(&self, interval: String) -> Result<String, CubeError> {
        Ok(format!("'{}'", interval))
    }

    fn add_timestamp_interval(&self, date: String, interval: String) -> Result<String, CubeError> {
        self.add_interval(date, interval)
    }

    fn interval_and_minimal_time_unit(&self, interval: String) -> Result<Vec<String>, CubeError> {
        let min_unit = if interval.contains("second") {
            "second"
        } else if interval.contains("minute") {
            "minute"
        } else if interval.contains("hour") {
            "hour"
        } else if interval.contains("day") {
            "day"
        } else if interval.contains("week") {
            "week"
        } else if interval.contains("month") {
            "month"
        } else if interval.contains("quarter") {
            "quarter"
        } else if interval.contains("year") {
            "year"
        } else {
            "day" // default
        };

        Ok(vec![interval, min_unit.to_string()])
    }

    fn hll_init(&self, sql: String) -> Result<String, CubeError> {
        Ok(format!("hll_add_agg(hll_hash_any({}))", sql))
    }

    fn hll_merge(&self, sql: String) -> Result<String, CubeError> {
        Ok(format!("merge({})", sql))
    }

    fn hll_cardinality_merge(&self, sql: String) -> Result<String, CubeError> {
        Ok(format!("cardinality(merge({}))", sql))
    }

    fn count_distinct_approx(&self, sql: String) -> Result<String, CubeError> {
        Ok(format!(
            "round(hll_cardinality(hll_add_agg(hll_hash_any({}))))",
            sql
        ))
    }

    fn support_generated_series_for_custom_td(&self) -> Result<bool, CubeError> {
        Ok(true)
    }

    fn date_bin(
        &self,
        interval: String,
        source: String,
        origin: String,
    ) -> Result<String, CubeError> {
        if self.cubestore {
            // Mirror CubeStoreQuery.dateBin: DATE_BIN over to_timestamp casts.
            return Ok(format!(
                "DATE_BIN(INTERVAL {}, to_timestamp({}), to_timestamp('{}'))",
                Self::format_interval(&interval),
                source,
                origin
            ));
        }
        Ok(format!(
            "('{}' ::timestamp + INTERVAL '{}' * FLOOR(EXTRACT(EPOCH FROM ({} - '{}'::timestamp)) / EXTRACT(EPOCH FROM INTERVAL '{}')))",
            origin, interval, source, origin, interval
        ))
    }
}
