use std::collections::HashMap;

use core_types::{ErrorCode, ErrorDomain, RtError};

use crate::compat::CompatibilityPolicy;
use crate::contract::{ContractKey, ResourceContract, ResourceKind};
use crate::schema::{SchemaDescriptor, SchemaId, SchemaVersion};

pub trait SchemaRegistry {
    fn register(&mut self, descriptor: SchemaDescriptor) -> Result<(), RtError>;
    fn get(&self, id: &SchemaId) -> Option<&SchemaDescriptor>;
    fn is_compatible(
        &self,
        id: &SchemaId,
        version: SchemaVersion,
        policy: CompatibilityPolicy,
    ) -> bool;
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodecDescriptor {
    pub schema_id: SchemaId,
    pub name: String,
    pub zero_copy: bool,
}

pub type SchemaCodecBinding = CodecDescriptor;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct ContractCodecBinding {
    pub resource_kind: ResourceKind,
    pub contract_hash: String,
    pub codec_family: String,
    pub codec_name: String,
}

pub trait CodecRegistry {
    fn register(&mut self, descriptor: CodecDescriptor) -> Result<(), RtError>;
    fn get(&self, schema_id: &SchemaId) -> Option<&CodecDescriptor>;
}

pub trait ContractCodecRegistry {
    fn register(&mut self, binding: ContractCodecBinding) -> Result<(), RtError>;
    fn get(
        &self,
        resource_kind: ResourceKind,
        contract_hash: &str,
    ) -> Option<&ContractCodecBinding>;
    fn list(&self) -> Vec<ContractCodecBinding>;
}

pub trait ContractRegistry {
    fn register(&mut self, key: ContractKey, contract: ResourceContract) -> Result<(), RtError>;
    fn get(&self, key: &ContractKey) -> Option<&ResourceContract>;
    fn list(&self) -> Vec<(ContractKey, ResourceContract)>;
}

#[derive(Default)]
pub struct SimpleSchemaRegistry {
    schemas: HashMap<SchemaId, SchemaDescriptor>,
}

impl SchemaRegistry for SimpleSchemaRegistry {
    fn register(&mut self, descriptor: SchemaDescriptor) -> Result<(), RtError> {
        if self.schemas.contains_key(&descriptor.id) {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::DataModel,
                false,
                "schema already registered",
            ));
        }
        self.schemas.insert(descriptor.id.clone(), descriptor);
        Ok(())
    }

    fn get(&self, id: &SchemaId) -> Option<&SchemaDescriptor> {
        self.schemas.get(id)
    }

    fn is_compatible(
        &self,
        id: &SchemaId,
        version: SchemaVersion,
        policy: CompatibilityPolicy,
    ) -> bool {
        self.get(id)
            .map(|descriptor| policy.check(descriptor.version, version))
            .unwrap_or(false)
    }
}

#[derive(Default)]
pub struct SimpleCodecRegistry {
    codecs: HashMap<SchemaId, CodecDescriptor>,
}

#[derive(Default)]
pub struct SimpleContractRegistry {
    contracts: HashMap<ContractKey, ResourceContract>,
}

#[derive(Default)]
pub struct SimpleContractCodecRegistry {
    bindings: HashMap<(ResourceKind, String), ContractCodecBinding>,
}

impl CodecRegistry for SimpleCodecRegistry {
    fn register(&mut self, descriptor: CodecDescriptor) -> Result<(), RtError> {
        if self.codecs.contains_key(&descriptor.schema_id) {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::DataModel,
                false,
                "codec already registered",
            ));
        }
        self.codecs.insert(descriptor.schema_id.clone(), descriptor);
        Ok(())
    }

    fn get(&self, schema_id: &SchemaId) -> Option<&CodecDescriptor> {
        self.codecs.get(schema_id)
    }
}

impl ContractRegistry for SimpleContractRegistry {
    fn register(&mut self, key: ContractKey, contract: ResourceContract) -> Result<(), RtError> {
        if let Some(existing) = self.contracts.get(&key) {
            if existing == &contract {
                return Ok(());
            }

            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::DataModel,
                false,
                format!(
                    "contract conflict for {} {}: existing_hash={} incoming_hash={}",
                    key.resource_kind.as_str(),
                    key.resolved_name,
                    existing.contract_hash(),
                    contract.contract_hash(),
                ),
            ));
        }

        self.contracts.insert(key, contract);
        Ok(())
    }

    fn get(&self, key: &ContractKey) -> Option<&ResourceContract> {
        self.contracts.get(key)
    }

    fn list(&self) -> Vec<(ContractKey, ResourceContract)> {
        let mut items = self
            .contracts
            .iter()
            .map(|(key, value)| (key.clone(), value.clone()))
            .collect::<Vec<_>>();
        items.sort_by(|a, b| a.0.cmp(&b.0));
        items
    }
}

impl ContractCodecRegistry for SimpleContractCodecRegistry {
    fn register(&mut self, binding: ContractCodecBinding) -> Result<(), RtError> {
        let key = (binding.resource_kind, binding.contract_hash.clone());
        if self.bindings.contains_key(&key) {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::DataModel,
                false,
                "contract codec binding already registered",
            ));
        }
        self.bindings.insert(key, binding);
        Ok(())
    }

    fn get(
        &self,
        resource_kind: ResourceKind,
        contract_hash: &str,
    ) -> Option<&ContractCodecBinding> {
        self.bindings
            .get(&(resource_kind, contract_hash.to_string()))
    }

    fn list(&self) -> Vec<ContractCodecBinding> {
        let mut items = self.bindings.values().cloned().collect::<Vec<_>>();
        items.sort_by(|a, b| {
            a.resource_kind
                .cmp(&b.resource_kind)
                .then_with(|| a.contract_hash.cmp(&b.contract_hash))
                .then_with(|| a.codec_family.cmp(&b.codec_family))
                .then_with(|| a.codec_name.cmp(&b.codec_name))
        });
        items
    }
}
