use anyhow::{Result, anyhow};
use rocksdb::{ColumnFamilyDescriptor, DB, IteratorMode, Options, WriteBatch};
use serde::de::DeserializeOwned;
use std::borrow::Borrow;
use std::path::Path;
use std::sync::Arc;
use tracing::{info, warn};

use crate::rtr::cache::{CacheAvailability, Delta, Snapshot};

const CF_META: &str = "meta";
const CF_SNAPSHOT: &str = "snapshot";
const CF_DELTA: &str = "delta";

const META_AVAILABILITY: &[u8] = b"availability";
const META_SESSION_ID_PREFIX: &str = "session_id_v";
const META_SERIAL_PREFIX: &str = "serial_v";
const META_DELTA_MIN_PREFIX: &str = "delta_min_v";
const META_DELTA_MAX_PREFIX: &str = "delta_max_v";
const SNAPSHOT_CURRENT_PREFIX: &str = "current_v";

const DELTA_KEY_V2_PREFIX: u8 = b'D';

fn delta_key_v2(version: u8, serial: u32) -> [u8; 6] {
    let mut key = [0u8; 6];
    key[0] = DELTA_KEY_V2_PREFIX;
    key[1] = version;
    key[2..].copy_from_slice(&serial.to_be_bytes());
    key
}

fn delta_prefix_start(version: u8) -> [u8; 6] {
    delta_key_v2(version, 0)
}

fn delta_prefix_end_exclusive(version: u8) -> [u8; 6] {
    delta_key_v2(version.saturating_add(1), 0)
}

fn delta_key_v2_serial(key: &[u8]) -> Option<(u8, u32)> {
    if key.len() != 6 || key[0] != DELTA_KEY_V2_PREFIX {
        return None;
    }
    let version = key[1];
    let mut bytes = [0u8; 4];
    bytes.copy_from_slice(&key[2..]);
    Some((version, u32::from_be_bytes(bytes)))
}

fn meta_key(prefix: &str, version: u8) -> Vec<u8> {
    format!("{}{}", prefix, version).into_bytes()
}

fn snapshot_key(version: u8) -> Vec<u8> {
    format!("{}{}", SNAPSHOT_CURRENT_PREFIX, version).into_bytes()
}

#[derive(Clone)]
pub struct RtrStore {
    db: Arc<DB>,
}

impl RtrStore {
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path_ref = path.as_ref();
        let mut opts = Options::default();
        opts.create_if_missing(true);
        opts.create_missing_column_families(true);

        let cfs = vec![
            ColumnFamilyDescriptor::new(CF_META, Options::default()),
            ColumnFamilyDescriptor::new(CF_SNAPSHOT, Options::default()),
            ColumnFamilyDescriptor::new(CF_DELTA, Options::default()),
        ];

        info!("opening RTR RocksDB store at {}", path_ref.display());
        let db = Arc::new(DB::open_cf_descriptors(&opts, path_ref, cfs)?);
        info!("opened RTR RocksDB store at {}", path_ref.display());
        Ok(Self { db })
    }

    fn get_cf<T: DeserializeOwned>(&self, cf: &str, key: &[u8]) -> Result<Option<T>> {
        let cf_handle = self
            .db
            .cf_handle(cf)
            .ok_or_else(|| anyhow!("CF not found"))?;
        if let Some(value) = self.db.get_cf(cf_handle, key)? {
            let obj = serde_json::from_slice(&value)?;
            Ok(Some(obj))
        } else {
            Ok(None)
        }
    }

    pub fn get_availability(&self) -> Result<Option<CacheAvailability>> {
        self.get_cf(CF_META, META_AVAILABILITY)
    }

    pub fn get_session_id_for_version(&self, version: u8) -> Result<Option<u16>> {
        let key = meta_key(META_SESSION_ID_PREFIX, version);
        self.get_cf(CF_META, &key)
    }

    pub fn get_serial_for_version(&self, version: u8) -> Result<Option<u32>> {
        let key = meta_key(META_SERIAL_PREFIX, version);
        self.get_cf(CF_META, &key)
    }

    pub fn get_delta_window_for_version(&self, version: u8) -> Result<Option<(u32, u32)>> {
        let min_key = meta_key(META_DELTA_MIN_PREFIX, version);
        let max_key = meta_key(META_DELTA_MAX_PREFIX, version);
        let min: Option<u32> = self.get_cf(CF_META, &min_key)?;
        let max: Option<u32> = self.get_cf(CF_META, &max_key)?;
        match (min, max) {
            (Some(min), Some(max)) => Ok(Some((min, max))),
            (None, None) => Ok(None),
            _ => Err(anyhow!(
                "Inconsistent DB state: delta window mismatch for version {}",
                version
            )),
        }
    }

    pub fn get_snapshot_for_version(&self, version: u8) -> Result<Option<Snapshot>> {
        let key = snapshot_key(version);
        self.get_cf(CF_SNAPSHOT, &key)
    }

    pub fn get_delta_for_version(&self, version: u8, serial: u32) -> Result<Option<Delta>> {
        self.get_cf(CF_DELTA, &delta_key_v2(version, serial))
    }

    pub fn load_delta_window_for_version(
        &self,
        version: u8,
        min_serial: u32,
        max_serial: u32,
    ) -> Result<Vec<Delta>> {
        let cf_handle = self
            .db
            .cf_handle(CF_DELTA)
            .ok_or_else(|| anyhow!("CF_DELTA not found"))?;
        let iter = self.db.iterator_cf(cf_handle, IteratorMode::Start);
        let mut out = Vec::new();
        for item in iter {
            let (key, value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
            let Some((key_version, serial)) = delta_key_v2_serial(key.as_ref()) else {
                continue;
            };
            if key_version != version {
                continue;
            }
            if serial_in_window(serial, min_serial, max_serial) {
                let delta: Delta = serde_json::from_slice(value.as_ref())?;
                out.push(delta);
            }
        }
        out.sort_by_key(|delta| delta.serial().wrapping_sub(min_serial));
        validate_delta_window(&out, min_serial, max_serial)?;
        Ok(out)
    }

    fn list_delta_keys_for_version(&self, version: u8) -> Result<Vec<Vec<u8>>> {
        let cf_handle = self
            .db
            .cf_handle(CF_DELTA)
            .ok_or_else(|| anyhow!("CF_DELTA not found"))?;
        let iter = self.db.iterator_cf(cf_handle, IteratorMode::Start);
        let mut keys = Vec::new();
        for item in iter {
            let (key, _value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
            if matches!(delta_key_v2_serial(key.as_ref()), Some((v, _)) if v == version) {
                keys.push(key.to_vec());
            }
        }
        Ok(keys)
    }

    fn list_delta_keys_outside_window_for_version(
        &self,
        version: u8,
        min_serial: u32,
        max_serial: u32,
    ) -> Result<Vec<Vec<u8>>> {
        let cf_handle = self
            .db
            .cf_handle(CF_DELTA)
            .ok_or_else(|| anyhow!("CF_DELTA not found"))?;
        let iter = self.db.iterator_cf(cf_handle, IteratorMode::Start);
        let mut keys = Vec::new();
        for item in iter {
            let (key, _value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
            let Some((key_version, serial)) = delta_key_v2_serial(key.as_ref()) else {
                continue;
            };
            if key_version != version {
                continue;
            }
            if !serial_in_window(serial, min_serial, max_serial) {
                keys.push(key.to_vec());
            }
        }
        Ok(keys)
    }

    /// Persist full versioned RTR cache state in one atomic batch.
    ///
    /// Write-boundary contract:
    /// - Production writes must go through `src/rtr/cache/store.rs`.
    /// - Direct callers should be limited to DB contract tests.
    /// - Do not introduce ad-hoc write paths outside cache/store, otherwise
    ///   session_id/serial/snapshot/delta_window consistency can be broken.
    pub fn save_cache_state_versioned<S>(
        &self,
        availability: CacheAvailability,
        snapshots: &[S; 3],
        session_ids: &[u16; 3],
        serials: &[u32; 3],
        deltas: &[Option<&Delta>; 3],
        delta_windows: &[Option<(u32, u32)>; 3],
        clear_delta_windows: &[bool; 3],
    ) -> Result<()>
    where
        S: Borrow<Snapshot>,
    {
        let snapshot_cf = self
            .db
            .cf_handle(CF_SNAPSHOT)
            .ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
        let meta_cf = self
            .db
            .cf_handle(CF_META)
            .ok_or_else(|| anyhow!("CF_META not found"))?;
        let delta_cf = self
            .db
            .cf_handle(CF_DELTA)
            .ok_or_else(|| anyhow!("CF_DELTA not found"))?;

        let mut batch = WriteBatch::default();
        batch.put_cf(
            meta_cf,
            META_AVAILABILITY,
            serde_json::to_vec(&availability)?,
        );

        for version in 0u8..=2 {
            let idx = version as usize;
            batch.put_cf(
                snapshot_cf,
                snapshot_key(version),
                serde_json::to_vec(snapshots[idx].borrow())?,
            );
            batch.put_cf(
                meta_cf,
                meta_key(META_SESSION_ID_PREFIX, version),
                serde_json::to_vec(&session_ids[idx])?,
            );
            batch.put_cf(
                meta_cf,
                meta_key(META_SERIAL_PREFIX, version),
                serde_json::to_vec(&serials[idx])?,
            );

            if let Some(delta) = deltas[idx] {
                batch.put_cf(
                    delta_cf,
                    delta_key_v2(version, delta.serial()),
                    serde_json::to_vec(delta)?,
                );
            }

            if clear_delta_windows[idx] {
                batch.delete_cf(meta_cf, meta_key(META_DELTA_MIN_PREFIX, version));
                batch.delete_cf(meta_cf, meta_key(META_DELTA_MAX_PREFIX, version));
                batch.delete_range_cf(
                    delta_cf,
                    delta_prefix_start(version),
                    delta_prefix_end_exclusive(version),
                );
            } else if let Some((min_serial, max_serial)) = delta_windows[idx] {
                batch.put_cf(
                    meta_cf,
                    meta_key(META_DELTA_MIN_PREFIX, version),
                    serde_json::to_vec(&min_serial)?,
                );
                batch.put_cf(
                    meta_cf,
                    meta_key(META_DELTA_MAX_PREFIX, version),
                    serde_json::to_vec(&max_serial)?,
                );
                if min_serial <= max_serial {
                    if min_serial > 0 {
                        batch.delete_range_cf(
                            delta_cf,
                            delta_prefix_start(version),
                            delta_key_v2(version, min_serial),
                        );
                    }
                    if max_serial < u32::MAX {
                        batch.delete_range_cf(
                            delta_cf,
                            delta_key_v2(version, max_serial.wrapping_add(1)),
                            delta_prefix_end_exclusive(version),
                        );
                    }
                } else {
                    for key in self
                        .list_delta_keys_outside_window_for_version(version, min_serial, max_serial)?
                    {
                        batch.delete_cf(delta_cf, key);
                    }
                }
            }
        }

        self.db.write(batch)?;
        Ok(())
    }
}

fn serial_in_window(serial: u32, min_serial: u32, max_serial: u32) -> bool {
    serial.wrapping_sub(min_serial) <= max_serial.wrapping_sub(min_serial)
}

fn validate_delta_window(deltas: &[Delta], min_serial: u32, max_serial: u32) -> Result<()> {
    if deltas.is_empty() {
        warn!(
            "RTR store delta window validation failed: no persisted deltas for window [{}, {}]",
            min_serial, max_serial
        );
        return Err(anyhow!(
            "delta window [{}, {}] has no persisted deltas",
            min_serial,
            max_serial
        ));
    }

    if deltas.first().map(Delta::serial) != Some(min_serial) {
        warn!(
            "RTR store delta window validation failed: first delta mismatch for window [{}, {}], got {:?}",
            min_serial,
            max_serial,
            deltas.first().map(Delta::serial)
        );
        return Err(anyhow!(
            "delta window starts at {}, but first persisted delta is {:?}",
            min_serial,
            deltas.first().map(Delta::serial)
        ));
    }

    if deltas.last().map(Delta::serial) != Some(max_serial) {
        warn!(
            "RTR store delta window validation failed: last delta mismatch for window [{}, {}], got {:?}",
            min_serial,
            max_serial,
            deltas.last().map(Delta::serial)
        );
        return Err(anyhow!(
            "delta window ends at {}, but last persisted delta is {:?}",
            max_serial,
            deltas.last().map(Delta::serial)
        ));
    }

    for pair in deltas.windows(2) {
        if pair[1].serial() != pair[0].serial().wrapping_add(1) {
            warn!(
                "RTR store delta window validation failed: non-contiguous pair {} -> {} within window [{}, {}]",
                pair[0].serial(),
                pair[1].serial(),
                min_serial,
                max_serial
            );
            return Err(anyhow!(
                "persisted deltas are not contiguous: {} -> {}",
                pair[0].serial(),
                pair[1].serial()
            ));
        }
    }

    Ok(())
}
