use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

use chio_core::federation::{
    validate_federated_open_admission_policy, FederatedOpenAdmissionPolicyArtifact,
    SignedFederatedOpenAdmissionPolicy,
};
use chio_core::listing::GenericTrustAdmissionClass;
use chio_core::sha256_hex;
use serde::{Deserialize, Serialize};

use crate::CliError;

pub const FEDERATION_ADMISSION_POLICY_RECORD_SCHEMA: &str =
    "chio.permissionless-federation-policy.v1";
pub const FEDERATION_ADMISSION_POLICY_REGISTRY_VERSION: &str =
    "chio.permissionless-federation-policy-registry.v1";

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionRateLimit {
    pub max_requests: u32,
    pub window_seconds: u64,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionAntiSybilControls {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rate_limit: Option<FederationAdmissionRateLimit>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proof_of_work_bits: Option<u8>,
    #[serde(default)]
    pub bond_backed_only: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionPolicyRecord {
    pub schema: String,
    pub published_at: u64,
    pub policy: SignedFederatedOpenAdmissionPolicy,
    #[serde(default)]
    pub anti_sybil: FederationAdmissionAntiSybilControls,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub minimum_reputation_score: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub note: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionPolicyRegistry {
    pub version: String,
    #[serde(default)]
    pub policies: BTreeMap<String, FederationAdmissionPolicyRecord>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionPolicyListResponse {
    pub configured: bool,
    pub count: usize,
    pub policies: Vec<FederationAdmissionPolicyRecord>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionPolicyDeleteResponse {
    pub policy_id: String,
    pub deleted: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionEvaluationRequest {
    pub policy_id: String,
    pub subject_key: String,
    pub requested_admission_class: GenericTrustAdmissionClass,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proof_of_work_nonce: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionRateLimitStatus {
    pub limit: u32,
    pub window_seconds: u64,
    pub remaining: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub retry_after_seconds: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FederationAdmissionEvaluationResponse {
    pub policy_id: String,
    pub subject_key: String,
    pub requested_admission_class: GenericTrustAdmissionClass,
    pub accepted: bool,
    pub decision_reason: String,
    pub proof_of_work_required: bool,
    pub proof_of_work_verified: bool,
    pub bond_backed_required: bool,
    pub bond_backed_satisfied: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub minimum_reputation_score: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub observed_reputation_score: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rate_limit: Option<FederationAdmissionRateLimitStatus>,
}

impl Default for FederationAdmissionPolicyRegistry {
    fn default() -> Self {
        Self {
            version: FEDERATION_ADMISSION_POLICY_REGISTRY_VERSION.to_string(),
            policies: BTreeMap::new(),
        }
    }
}

impl FederationAdmissionPolicyRegistry {
    pub fn load(path: &Path) -> Result<Self, CliError> {
        match fs::read(path) {
            Ok(bytes) => {
                let mut registry: Self = serde_json::from_slice(&bytes)?;
                if registry.version != FEDERATION_ADMISSION_POLICY_REGISTRY_VERSION {
                    return Err(CliError::Other(format!(
                        "unsupported federation admission policy registry version: {}",
                        registry.version
                    )));
                }
                registry.version = FEDERATION_ADMISSION_POLICY_REGISTRY_VERSION.to_string();
                for record in registry.policies.values() {
                    verify_federation_admission_policy_record(record)?;
                }
                Ok(registry)
            }
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
            Err(error) => Err(CliError::Io(error)),
        }
    }

    pub fn save(&self, path: &Path) -> Result<(), CliError> {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(path, serde_json::to_vec_pretty(self)?)?;
        Ok(())
    }

    pub fn get(&self, policy_id: &str) -> Option<&FederationAdmissionPolicyRecord> {
        self.policies.get(policy_id)
    }

    pub fn upsert(&mut self, record: FederationAdmissionPolicyRecord) -> Result<(), CliError> {
        verify_federation_admission_policy_record(&record)?;
        self.policies
            .insert(record.policy.body.policy_id.clone(), record);
        Ok(())
    }

    pub fn remove(&mut self, policy_id: &str) -> bool {
        self.policies.remove(policy_id).is_some()
    }
}

pub fn verify_federation_admission_policy_record(
    record: &FederationAdmissionPolicyRecord,
) -> Result<(), CliError> {
    if record.schema != FEDERATION_ADMISSION_POLICY_RECORD_SCHEMA {
        return Err(CliError::Other(format!(
            "unsupported federation admission policy schema: {}",
            record.schema
        )));
    }
    if !record.policy.verify_signature()? {
        return Err(CliError::Other(
            "federation admission policy signature verification failed".to_string(),
        ));
    }
    validate_federated_open_admission_policy(&record.policy.body)
        .map_err(|error| CliError::Other(error.to_string()))?;
    validate_anti_sybil_controls(&record.anti_sybil, &record.policy.body)?;
    if let Some(score) = record.minimum_reputation_score {
        if !score.is_finite() || !(0.0..=1.0).contains(&score) {
            return Err(CliError::Other(
                "minimum_reputation_score must be between 0.0 and 1.0".to_string(),
            ));
        }
    }
    Ok(())
}

pub fn verify_admission_proof_of_work(
    policy_id: &str,
    subject_key: &str,
    nonce: &str,
    difficulty_bits: u8,
) -> bool {
    if difficulty_bits == 0 || nonce.trim().is_empty() {
        return false;
    }
    let digest = sha256_hex(format!("{policy_id}:{subject_key}:{nonce}").as_bytes());
    leading_zero_bits(&digest) >= u32::from(difficulty_bits)
}

fn validate_anti_sybil_controls(
    controls: &FederationAdmissionAntiSybilControls,
    policy: &FederatedOpenAdmissionPolicyArtifact,
) -> Result<(), CliError> {
    if let Some(limit) = controls.rate_limit.as_ref() {
        if limit.max_requests == 0 {
            return Err(CliError::Other(
                "anti_sybil.rate_limit.max_requests must be non-zero".to_string(),
            ));
        }
        if limit.window_seconds == 0 {
            return Err(CliError::Other(
                "anti_sybil.rate_limit.window_seconds must be non-zero".to_string(),
            ));
        }
    }
    if let Some(bits) = controls.proof_of_work_bits {
        if bits == 0 || bits > 24 {
            return Err(CliError::Other(
                "anti_sybil.proof_of_work_bits must be between 1 and 24".to_string(),
            ));
        }
    }
    if controls.bond_backed_only
        && !policy
            .allowed_admission_classes
            .contains(&GenericTrustAdmissionClass::BondBacked)
    {
        return Err(CliError::Other(
            "bond_backed_only requires the signed policy to allow bond_backed admission"
                .to_string(),
        ));
    }
    Ok(())
}

fn leading_zero_bits(hex_digest: &str) -> u32 {
    let mut bits = 0_u32;
    for ch in hex_digest.chars() {
        let Some(value) = ch.to_digit(16) else {
            break;
        };
        if value == 0 {
            bits += 4;
            continue;
        }
        bits += value.leading_zeros().saturating_sub(28);
        break;
    }
    bits
}
