use serde::{Serialize, Serializer, ser::SerializeMap};
use xxhash_rust::xxh3::Xxh3;

use crate::scalar::ScalarTyp;

pub type NamedField = (&'static str, Typ);
pub type Arr<T> = &'static [T];
pub type Str = &'static str;
pub type Map<T> = Arr<(u8, T)>;

#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
#[serde(tag = "type", content = "data")]
pub enum Fields {
    Named(Arr<NamedField>),
    Unnamed(Arr<Typ>),
}

/**
named structs, unnamed structs and tuples
если нет названий полей в структуре, то это неименованная структура или кортеж.
*/
#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
pub struct StructType {
    pub name: &'static str,
    pub fields: Fields,
}

fn variants<S, T>(variants: Map<T>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
    T: Serialize,
{
    let mut map = serializer.serialize_map(Some(variants.len()))?;
    for (key, value) in variants {
        map.serialize_entry(key, value)?;
    }
    map.end()
}

#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
pub struct EnumType {
    pub name: Str,
    #[serde(serialize_with = "variants")]
    pub variants: Map<NamedField>,
}

#[derive(PartialEq, Eq, Clone, Serialize, Debug, Copy, Hash)]
pub struct SimpleEnumType {
    pub name: Str,
    #[serde(serialize_with = "variants")]
    pub variants: Map<Str>,
}

#[derive(PartialEq, Clone, Debug, Copy, Hash)]
pub enum Typ {
    Scalar(ScalarTyp),
    Array(u32, &'static Typ),
    Vec(&'static Typ),
    Optional(&'static Typ),
    SimpleEnum(SimpleEnumType),
    Struct(StructType),
    Enum(EnumType),
    Custom(&'static str, &'static [Typ]),
}

/// Composite-only mirror for serde; leaves serialize via [`ScalarTyp`] (flat wire).
#[derive(Serialize)]
#[serde(tag = "type", content = "data")]
enum TypComposite {
    Array(u32, &'static Typ),
    Vec(&'static Typ),
    Optional(&'static Typ),
    SimpleEnum(SimpleEnumType),
    Struct(StructType),
    Enum(EnumType),
    Custom(&'static str, &'static [Typ]),
}

impl Serialize for Typ {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match self {
            Typ::Scalar(s) => s.serialize(serializer),
            Typ::Array(n, inner) => TypComposite::Array(*n, inner).serialize(serializer),
            Typ::Vec(inner) => TypComposite::Vec(inner).serialize(serializer),
            Typ::Optional(inner) => TypComposite::Optional(inner).serialize(serializer),
            Typ::SimpleEnum(e) => TypComposite::SimpleEnum(*e).serialize(serializer),
            Typ::Struct(s) => TypComposite::Struct(*s).serialize(serializer),
            Typ::Enum(e) => TypComposite::Enum(*e).serialize(serializer),
            Typ::Custom(name, args) => TypComposite::Custom(name, args).serialize(serializer),
        }
    }
}

/// Format tag prefixing every `Typ::h` digest. Bump the trailing version when the
/// canonical encoding below changes (it invalidates all persisted/protocol hashes).
const HASH_FORMAT_TAG: &[u8] = b"armour-typ/typ/v1";

/// Write a length as a fixed-width `u64` LE — NEVER `usize`, so the digest is
/// independent of the target's pointer width (`wasm32` vs 64-bit). This is the
/// whole point of the manual traversal; see `width_independent` test below.
#[inline]
fn hash_len(hasher: &mut Xxh3, len: usize) {
    hasher.update(&(len as u64).to_le_bytes());
}

#[inline]
fn hash_str(hasher: &mut Xxh3, value: &str) {
    hash_len(hasher, value.len());
    hasher.update(value.as_bytes());
}

fn hash_scalar(hasher: &mut Xxh3, scalar: &ScalarTyp) {
    use ScalarTyp::*;
    // One explicit u8 tag per variant — do NOT delegate to derive `Hash`, whose
    // enum discriminant is an `isize` (width-dependent via `write_isize`).
    let tag: u8 = match scalar {
        Bool => 0,
        U8 => 1,
        U16 => 2,
        U32 => 3,
        U64 => 4,
        I32 => 5,
        I64 => 6,
        F32 => 7,
        F64 => 8,
        Str => 9,
        Datetime => 10,
        Timestamp => 11,
        Decimal => 12,
        Id32 => 13,
        Id64 => 14,
        Fuid => 15,
        LowId => 16,
        Bytes => 17,
        Void => 18,
        RustJson => 19,
        JsonBytes => 20,
        ArrayBytes(_) => 21,
    };
    hasher.update(&[tag]);
    if let ArrayBytes(n) = scalar {
        hasher.update(&n.to_le_bytes());
    }
}

fn hash_fields(hasher: &mut Xxh3, fields: &Fields) {
    match fields {
        Fields::Named(items) => {
            hasher.update(&[0u8]);
            hash_len(hasher, items.len());
            for (name, typ) in *items {
                hash_str(hasher, name);
                hash_typ(hasher, typ);
            }
        }
        Fields::Unnamed(items) => {
            hasher.update(&[1u8]);
            hash_len(hasher, items.len());
            for typ in *items {
                hash_typ(hasher, typ);
            }
        }
    }
}

fn hash_typ(hasher: &mut Xxh3, typ: &Typ) {
    match typ {
        Typ::Scalar(s) => {
            hasher.update(&[0u8]);
            hash_scalar(hasher, s);
        }
        Typ::Array(n, inner) => {
            hasher.update(&[1u8]);
            hasher.update(&n.to_le_bytes());
            hash_typ(hasher, inner);
        }
        Typ::Vec(inner) => {
            hasher.update(&[2u8]);
            hash_typ(hasher, inner);
        }
        Typ::Optional(inner) => {
            hasher.update(&[3u8]);
            hash_typ(hasher, inner);
        }
        Typ::SimpleEnum(e) => {
            hasher.update(&[4u8]);
            hash_str(hasher, e.name);
            hash_len(hasher, e.variants.len());
            for (discr, name) in e.variants {
                hasher.update(&[*discr]);
                hash_str(hasher, name);
            }
        }
        Typ::Struct(s) => {
            hasher.update(&[5u8]);
            hash_str(hasher, s.name);
            hash_fields(hasher, &s.fields);
        }
        Typ::Enum(e) => {
            hasher.update(&[6u8]);
            hash_str(hasher, e.name);
            hash_len(hasher, e.variants.len());
            for (discr, (name, payload)) in e.variants {
                hasher.update(&[*discr]);
                hash_str(hasher, name);
                hash_typ(hasher, payload);
            }
        }
        Typ::Custom(name, args) => {
            hasher.update(&[7u8]);
            hash_str(hasher, name);
            hash_len(hasher, args.len());
            for arg in *args {
                hash_typ(hasher, arg);
            }
        }
    }
}

impl Typ {
    /// Schema hash persisted in armdb (`db.info`/`CollectionInfo`) and checked on
    /// open, and used as the schema-negotiation fast-path hint.
    ///
    /// The traversal is **pointer-width-independent by construction**: it only ever
    /// feeds fixed-width bytes into the hasher (u8 tags, `u64` LE lengths, `u32` LE
    /// scalar counts, raw string bytes). It deliberately does NOT use derive
    /// `std::hash::Hash`, whose `[T]` length prefix (`write_usize`) and enum
    /// discriminant (`write_isize`) differ between `wasm32` and 64-bit targets.
    ///
    /// Byte output MUST stay stable; drift is caught by the golden test below.
    pub fn h(&self) -> u64 {
        let mut hasher = Xxh3::new();
        hasher.update(HASH_FORMAT_TAG);
        hash_typ(&mut hasher, self);
        hasher.digest()
    }
}

/// Public schema-version hash used by hash-first schema negotiation.
///
/// This is the same deterministic xxh3-based hash as [`Typ::h`]. It is not a
/// cryptographic hash; callers must treat equal hashes as a fast-path hint and
/// retain schema-based fallback behavior for peer mismatches.
#[inline]
pub fn schema_hash(t: &Typ) -> u64 {
    t.h()
}

#[cfg(test)]
mod golden {
    use super::*;
    use crate::scalar::ScalarTyp;

    /// One entry per `Typ` variant family. The third field is the expected
    /// `typ_hash`, captured once from `print_golden` (Step 5). NEVER hand-edit
    /// these values to silence a failure — a mismatch means the persisted
    /// schema-hash format drifted and must be investigated.
    fn golden_cases() -> Vec<(&'static str, Typ, u64)> {
        use ScalarTyp::*;
        vec![
            ("Bool", Typ::Scalar(Bool), 0x844969503cd52142),
            ("U8", Typ::Scalar(U8), 0xc8cadc679de83264),
            ("U16", Typ::Scalar(U16), 0x31f140c9e9d24a6a),
            ("U32", Typ::Scalar(U32), 0xb6e382b85cc8ad16),
            ("U64", Typ::Scalar(U64), 0x3e2513ed9422dbf3),
            ("I32", Typ::Scalar(I32), 0x3eed686985c17e30),
            ("I64", Typ::Scalar(I64), 0x6dcdfb5efac1eff5),
            ("F32", Typ::Scalar(F32), 0xd4256e6355df3aa6),
            ("F64", Typ::Scalar(F64), 0x25b32f6d7a5db6d7),
            ("Str", Typ::Scalar(Str), 0x2aa6e56e9ce67c6d),
            ("Datetime", Typ::Scalar(Datetime), 0xf789bd4fa70b626b),
            ("Timestamp", Typ::Scalar(Timestamp), 0x31205013816d027c),
            ("Decimal", Typ::Scalar(Decimal), 0x8bda406d6c8e133b),
            ("Id32", Typ::Scalar(Id32), 0x30e0670bf6dea824),
            ("Id64", Typ::Scalar(Id64), 0xc1c4537aede104ae),
            ("Fuid", Typ::Scalar(Fuid), 0x313781a3f9e8ffbb),
            ("LowId", Typ::Scalar(LowId), 0x09b9aaafe34aeb45),
            ("Bytes", Typ::Scalar(Bytes), 0x08276244715f6ff1),
            ("Void", Typ::Scalar(Void), 0xed774f18fb9a6e91),
            ("RustJson", Typ::Scalar(RustJson), 0x3874a1693f60d5d7),
            ("JsonBytes", Typ::Scalar(JsonBytes), 0xb4ccb5d58b3e668b),
            ("ArrayBytes", Typ::Scalar(ArrayBytes(4)), 0x58f9ebfb19da8930),
            (
                "Array",
                Typ::Array(3, &Typ::Scalar(U64)),
                0xe8fb2cd993687efe,
            ),
            ("Vec", Typ::Vec(&Typ::Scalar(U64)), 0x0def01338fe2656c),
            (
                "Optional",
                Typ::Optional(&Typ::Scalar(U64)),
                0xa03ca0cb992f08da,
            ),
            (
                "SimpleEnum",
                Typ::SimpleEnum(SimpleEnumType {
                    name: "SE",
                    variants: &[(0, "A"), (1, "B")],
                }),
                0x52315acdab3124db,
            ),
            (
                "StructNamed",
                Typ::Struct(StructType {
                    name: "S",
                    fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Str))]),
                }),
                0xa453f5fe90303c85,
            ),
            (
                "StructUnnamed",
                Typ::Struct(StructType {
                    name: "",
                    fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
                }),
                0x067f1d66238d5e94,
            ),
            (
                "Enum",
                Typ::Enum(EnumType {
                    name: "E",
                    variants: &[(0, ("A", Typ::Scalar(U64))), (1, ("B", Typ::Scalar(Bool)))],
                }),
                0x9d717d3549fb9528,
            ),
            (
                "Custom",
                Typ::Custom("C", &[Typ::Scalar(U64), Typ::Scalar(Str)]),
                0x04ff19c55360b278,
            ),
        ]
    }

    /// Run explicitly to (re)generate golden values:
    /// `cargo test -p armour-typ golden::print_golden -- --ignored --nocapture`
    #[test]
    #[ignore]
    fn print_golden() {
        for (name, t, _) in golden_cases() {
            println!("(\"{name}\", …, 0x{:016x}),", t.h());
        }
    }

    #[test]
    fn golden_typ_hash() {
        for (name, t, expected) in golden_cases() {
            assert_eq!(t.h(), expected, "typ_hash drift for variant `{name}`");
        }
    }

    /// Exhaustiveness: new `ScalarTyp` variant fails to compile without a golden case.
    #[allow(dead_code)]
    fn _scalar_guard(s: &ScalarTyp) {
        use ScalarTyp::*;
        match s {
            Bool | U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 | Str | Datetime | Timestamp
            | Decimal | Id32 | Id64 | Fuid | LowId | Bytes | Void | RustJson | JsonBytes => {}
            ArrayBytes(_) => {}
        }
    }

    /// Exhaustiveness: new composite `Typ` variant fails to compile without a golden case.
    #[allow(dead_code)]
    fn _typ_guard(t: &Typ) {
        match t {
            Typ::Scalar(_) => {}
            Typ::Array(_, _) => {}
            Typ::Vec(_) => {}
            Typ::Optional(_) => {}
            Typ::SimpleEnum(_) => {}
            Typ::Struct(_) => {}
            Typ::Enum(_) => {}
            Typ::Custom(_, _) => {}
        }
    }
}

#[cfg(test)]
#[allow(dead_code)]
mod schema_hash_tests {
    use super::*;
    use crate::get_type::GetType;
    use crate::scalar::ScalarTyp;

    #[derive(crate::GetType)]
    struct NamedOrderA {
        first: u32,
        second: u64,
    }

    #[derive(crate::GetType)]
    struct NamedOrderB {
        second: u64,
        first: u32,
    }

    #[test]
    fn free_schema_hash_matches_typ_h() {
        let typ = Typ::Vec(&Typ::Scalar(ScalarTyp::U64));
        assert_eq!(schema_hash(&typ), typ.h());
    }

    #[test]
    fn get_type_schema_hash_matches_type_constant_hash() {
        assert_eq!(<u32 as GetType>::schema_hash(), <u32 as GetType>::TYPE.h());
        assert_eq!(
            <NamedOrderA as GetType>::schema_hash(),
            <NamedOrderA as GetType>::TYPE.h()
        );
    }

    #[test]
    fn reordered_named_fields_have_distinct_hashes() {
        assert_ne!(
            <NamedOrderA as GetType>::schema_hash(),
            <NamedOrderB as GetType>::schema_hash()
        );
    }

    // --- distinctness invariants the manual traversal must preserve ---------

    #[test]
    fn named_and_unnamed_fields_differ() {
        use ScalarTyp::*;
        let named = Typ::Struct(StructType {
            name: "S",
            fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Bool))]),
        });
        let unnamed = Typ::Struct(StructType {
            name: "S",
            fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
        });
        assert_ne!(named.h(), unnamed.h());
    }

    #[test]
    fn scalar_variants_are_distinct() {
        use ScalarTyp::*;
        // A length-prefixed-only fix would still collide same-shape scalars; the
        // per-variant u8 tag keeps them apart.
        assert_ne!(Typ::Scalar(U32).h(), Typ::Scalar(I32).h());
        assert_ne!(Typ::Scalar(U64).h(), Typ::Scalar(Timestamp).h());
    }

    #[test]
    fn enum_variant_discriminant_value_matters() {
        use ScalarTyp::*;
        let a = Typ::Enum(EnumType {
            name: "E",
            variants: &[(0, ("A", Typ::Scalar(U64)))],
        });
        let b = Typ::Enum(EnumType {
            name: "E",
            variants: &[(1, ("A", Typ::Scalar(U64)))],
        });
        assert_ne!(a.h(), b.h());
    }

    #[test]
    fn scalar_and_array_counts_matter() {
        use ScalarTyp::*;
        assert_ne!(
            Typ::Scalar(ArrayBytes(4)).h(),
            Typ::Scalar(ArrayBytes(8)).h()
        );
        assert_ne!(
            Typ::Array(3, &Typ::Scalar(U64)).h(),
            Typ::Array(4, &Typ::Scalar(U64)).h()
        );
    }
}
