use std::fs;
use std::sync::{OnceLock, RwLock};

use crate::device;
use crate::error::LicenseError;
use crate::license::{License, LicenseFeature};
use crate::telemetry::{self, LocalState};

/// Global license guard — RwLock so `activate()` can upgrade from Community to Pro.
static GUARD: OnceLock<RwLock<LicenseGuard>> = OnceLock::new();

#[derive(Debug)]
pub struct LicenseGuard {
    license: Option<License>,
    state: LocalState,
}

impl LicenseGuard {
    /// Check if a Pro feature is allowed.
    pub fn check(&self, feature: &str) -> Result<(), LicenseError> {
        if !crate::license::requires_pro(feature) {
            return Ok(());
        }

        let license = self
            .license
            .as_ref()
            .ok_or_else(|| LicenseError::FeatureNotLicensed(feature.to_string()))?;

        if !license.has_feature(&LicenseFeature::Pro) {
            return Err(LicenseError::FeatureNotLicensed(feature.to_string()));
        }

        if self.state.grace_period_expired() {
            return Err(LicenseError::ConnectionRequired);
        }

        Ok(())
    }

    /// Whether we have a valid Pro license.
    pub fn is_pro(&self) -> bool {
        self.license
            .as_ref()
            .map(|l| l.has_feature(&LicenseFeature::Pro))
            .unwrap_or(false)
            && !self.state.grace_period_expired()
    }

    /// Get the max Monte Carlo sims allowed.
    pub fn max_monte_carlo_sims(&self) -> usize {
        if self.is_pro() {
            usize::MAX
        } else {
            1_000
        }
    }

    /// Get the minimum allowed output resolution (in seconds).
    pub fn min_resolution_secs(&self) -> u64 {
        if self.is_pro() {
            60
        } else {
            86_400
        }
    }
}

fn build_guard(license_key: Option<&str>) -> LicenseGuard {
    let license = license_key
        .and_then(|key| {
            let _ = fs::create_dir_all(device::license_dir());
            let _ = fs::write(device::license_file(), key);
            License::from_key(key).ok()
        })
        .or_else(|| {
            fs::read_to_string(device::license_file())
                .ok()
                .and_then(|key| License::from_key(&key).ok())
        });

    let state = LocalState::load_or_create();

    if let Some(ref lic) = license {
        telemetry::spawn_ping(
            lic.id_hash(),
            lic.payload.license_id.clone(),
            lic.payload.email.clone(),
            state.device_hash.clone(),
        );
    }

    LicenseGuard { license, state }
}

/// Initialize the license system. Call once at startup.
pub fn init_guard(license_key: Option<&str>) -> bool {
    let rw = GUARD.get_or_init(|| RwLock::new(build_guard(license_key)));
    rw.read().unwrap().is_pro()
}

/// Activate a license key at runtime (e.g. from Python `bt.activate(key)`).
/// Replaces the current guard with a new one containing the license.
pub fn activate(license_key: &str) -> Result<bool, LicenseError> {
    let rw = GUARD.get_or_init(|| RwLock::new(build_guard(None)));
    let new_guard = build_guard(Some(license_key));
    let is_pro = new_guard.is_pro();
    if !is_pro {
        return Err(LicenseError::InvalidKey);
    }
    *rw.write().unwrap() = new_guard;
    Ok(is_pro)
}

/// Quick feature check.
pub fn check_feature(feature: &str) -> Result<(), LicenseError> {
    let rw = GUARD.get_or_init(|| RwLock::new(build_guard(None)));
    rw.read().unwrap().check(feature)
}

/// Read-lock the global guard and call a closure with it.
pub fn with_guard<F, R>(f: F) -> R
where
    F: FnOnce(&LicenseGuard) -> R,
{
    let rw = GUARD.get_or_init(|| RwLock::new(build_guard(None)));
    let guard = rw.read().unwrap();
    f(&guard)
}

/// Get whether the current license is Pro.
pub fn is_pro() -> bool {
    with_guard(|g| g.is_pro())
}

/// Get max Monte Carlo sims.
pub fn max_monte_carlo_sims() -> usize {
    with_guard(|g| g.max_monte_carlo_sims())
}

/// Get min resolution in seconds.
pub fn min_resolution_secs() -> u64 {
    with_guard(|g| g.min_resolution_secs())
}

/// Downgrade to Community tier.
/// Called when the server reports this device exceeds the device limit.
pub fn downgrade_to_community() {
    let rw = GUARD.get_or_init(|| RwLock::new(build_guard(None)));
    let mut guard = rw.write().unwrap();
    guard.license = None;
}

/// Get license info: (tier, email).
pub fn license_info() -> (String, Option<String>) {
    with_guard(|g| {
        if g.is_pro() {
            let email = g.license.as_ref().map(|l| l.payload.email.clone());
            ("Pro".to_string(), email)
        } else {
            ("Community".to_string(), None)
        }
    })
}
