use md_codec::chunk::ChunkHeader;
use md_codec::header::Header;
use md_codec::identity::{Md1EncodingId, WalletDescriptorTemplateId, WalletPolicyId};
use serde::Serialize;

pub const SCHEMA: &str = "md-cli/1";

fn hex(bytes: &[u8]) -> String {
    let mut s = String::with_capacity(bytes.len() * 2);
    for b in bytes {
        use std::fmt::Write as _;
        write!(s, "{b:02x}").unwrap();
    }
    s
}

// Mirror surface added in v0.31.0; not yet wired into the JSON emit path.
#[allow(dead_code)]
#[derive(Serialize)]
pub struct JsonHeader {
    pub version: u8,
    pub divergent_paths: bool,
}
impl From<&Header> for JsonHeader {
    fn from(h: &Header) -> Self {
        Self {
            version: h.version,
            divergent_paths: h.divergent_paths,
        }
    }
}

#[allow(dead_code)]
#[derive(Serialize)]
pub struct JsonChunkHeader {
    pub version: u8,
    pub chunk_set_id: String,
    pub count: u8,
    pub index: u8,
}
impl From<&ChunkHeader> for JsonChunkHeader {
    fn from(h: &ChunkHeader) -> Self {
        Self {
            version: h.version,
            chunk_set_id: format!("0x{:05x}", h.chunk_set_id),
            count: h.count,
            index: h.index,
        }
    }
}

#[derive(Serialize)]
pub struct JsonHash {
    pub hex: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fingerprint: Option<String>,
}
impl From<&Md1EncodingId> for JsonHash {
    fn from(id: &Md1EncodingId) -> Self {
        Self {
            hex: hex(id.as_bytes()),
            fingerprint: None,
        }
    }
}
impl From<&WalletDescriptorTemplateId> for JsonHash {
    fn from(id: &WalletDescriptorTemplateId) -> Self {
        Self {
            hex: hex(id.as_bytes()),
            fingerprint: None,
        }
    }
}
impl From<&WalletPolicyId> for JsonHash {
    fn from(id: &WalletPolicyId) -> Self {
        // WalletPolicyId has no fingerprint() method — slice as_bytes() directly.
        let b = id.as_bytes();
        Self {
            hex: hex(b),
            fingerprint: Some(format!(
                "0x{:02x}{:02x}{:02x}{:02x}",
                b[0], b[1], b[2], b[3]
            )),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn schema_constant() {
        assert_eq!(SCHEMA, "md-cli/1");
    }

    #[test]
    fn header_serializes() {
        let h = Header {
            version: 0,
            divergent_paths: false,
        };
        let v = serde_json::to_value(JsonHeader::from(&h)).unwrap();
        assert_eq!(v["version"], 0);
        assert_eq!(v["divergent_paths"], false);
    }

    #[test]
    fn chunk_header_csid_formatted() {
        let h = ChunkHeader {
            version: 0,
            chunk_set_id: 0xABCDE,
            count: 3,
            index: 1,
        };
        let v = serde_json::to_value(JsonChunkHeader::from(&h)).unwrap();
        assert_eq!(v["chunk_set_id"], "0xabcde");
    }
}

use md_codec::encode::Descriptor;
use md_codec::origin_path::{OriginPath, PathDecl, PathDeclPaths};
use md_codec::tag::Tag;
use md_codec::tlv::TlvSection;
use md_codec::tree::{Body, Node};
use md_codec::use_site_path::UseSitePath;

/// Serde mirror of [`md_codec::tag::Tag`] anchoring the JSON `tag` field
/// to an explicit PascalCase contract — independent of `Tag`'s `Debug` impl.
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) enum JsonTag {
    Wpkh,
    Wsh,
    Pkh,
    Sh,
    AndV,
    AndB,
    AndOr,
    OrB,
    OrC,
    OrD,
    OrI,
    PkK,
    PkH,
    Multi,
    MultiA,
    SortedMulti,
    SortedMultiA,
    Thresh,
    Tr,
    Check,
    Verify,
    Swap,
    Alt,
    DupIf,
    NonZero,
    ZeroNotEqual,
    After,
    Older,
    Sha256,
    TapTree,
    Hash160,
    Hash256,
    Ripemd160,
    RawPkH,
    False,
    True,
}

impl From<&Tag> for JsonTag {
    fn from(t: &Tag) -> Self {
        match t {
            Tag::Wpkh => JsonTag::Wpkh,
            Tag::Wsh => JsonTag::Wsh,
            Tag::Pkh => JsonTag::Pkh,
            Tag::Sh => JsonTag::Sh,
            Tag::AndV => JsonTag::AndV,
            Tag::AndB => JsonTag::AndB,
            Tag::AndOr => JsonTag::AndOr,
            Tag::OrB => JsonTag::OrB,
            Tag::OrC => JsonTag::OrC,
            Tag::OrD => JsonTag::OrD,
            Tag::OrI => JsonTag::OrI,
            Tag::PkK => JsonTag::PkK,
            Tag::PkH => JsonTag::PkH,
            Tag::Multi => JsonTag::Multi,
            Tag::MultiA => JsonTag::MultiA,
            Tag::SortedMulti => JsonTag::SortedMulti,
            Tag::SortedMultiA => JsonTag::SortedMultiA,
            Tag::Thresh => JsonTag::Thresh,
            Tag::Tr => JsonTag::Tr,
            Tag::Check => JsonTag::Check,
            Tag::Verify => JsonTag::Verify,
            Tag::Swap => JsonTag::Swap,
            Tag::Alt => JsonTag::Alt,
            Tag::DupIf => JsonTag::DupIf,
            Tag::NonZero => JsonTag::NonZero,
            Tag::ZeroNotEqual => JsonTag::ZeroNotEqual,
            Tag::After => JsonTag::After,
            Tag::Older => JsonTag::Older,
            Tag::Sha256 => JsonTag::Sha256,
            Tag::TapTree => JsonTag::TapTree,
            Tag::Hash160 => JsonTag::Hash160,
            Tag::Hash256 => JsonTag::Hash256,
            Tag::Ripemd160 => JsonTag::Ripemd160,
            Tag::RawPkH => JsonTag::RawPkH,
            Tag::False => JsonTag::False,
            Tag::True => JsonTag::True,
        }
    }
}

#[derive(Serialize)]
pub struct JsonDescriptor {
    pub n: u8,
    pub path_decl: JsonPathDecl,
    pub use_site_path: JsonUseSitePath,
    pub tree: JsonNode,
    pub tlv: JsonTlv,
}
impl From<&Descriptor> for JsonDescriptor {
    fn from(d: &Descriptor) -> Self {
        Self {
            n: d.n,
            path_decl: (&d.path_decl).into(),
            use_site_path: (&d.use_site_path).into(),
            tree: (&d.tree).into(),
            tlv: (&d.tlv).into(),
        }
    }
}

#[derive(Serialize)]
#[serde(tag = "tag", content = "data")]
pub enum JsonPathDecl {
    Shared(String),
    Divergent(Vec<String>),
}
impl From<&PathDecl> for JsonPathDecl {
    fn from(p: &PathDecl) -> Self {
        match &p.paths {
            PathDeclPaths::Shared(op) => JsonPathDecl::Shared(format_origin_path(op)),
            PathDeclPaths::Divergent(v) => {
                JsonPathDecl::Divergent(v.iter().map(format_origin_path).collect())
            }
        }
    }
}

/// Render an `OriginPath` as `m/...` notation. v0.14's `OriginPath` does not
/// implement `Display`, so we format it here.
fn format_origin_path(p: &OriginPath) -> String {
    let mut s = String::from("m");
    for c in &p.components {
        s.push('/');
        s.push_str(&c.value.to_string());
        if c.hardened {
            s.push('\'');
        }
    }
    s
}

#[derive(Serialize)]
pub struct JsonUseSitePath {
    pub multipath: Option<Vec<JsonAlt>>,
    pub wildcard_hardened: bool,
}
#[derive(Serialize)]
pub struct JsonAlt {
    pub hardened: bool,
    pub value: u32,
}
impl From<&UseSitePath> for JsonUseSitePath {
    fn from(u: &UseSitePath) -> Self {
        Self {
            multipath: u.multipath.as_ref().map(|alts| {
                alts.iter()
                    .map(|a| JsonAlt {
                        hardened: a.hardened,
                        value: a.value,
                    })
                    .collect()
            }),
            wildcard_hardened: u.wildcard_hardened,
        }
    }
}

#[derive(Serialize)]
pub struct JsonNode {
    pub tag: JsonTag,
    pub body: JsonBody,
}
impl From<&Node> for JsonNode {
    fn from(n: &Node) -> Self {
        Self {
            tag: JsonTag::from(&n.tag),
            body: (&n.body).into(),
        }
    }
}

#[derive(Serialize)]
#[serde(tag = "kind", content = "data")]
pub enum JsonBody {
    KeyArg {
        index: u8,
    },
    Children(Vec<JsonNode>),
    Variable {
        k: u8,
        children: Vec<JsonNode>,
    },
    /// v0.30 Phase C: multi-family bodies (Multi/SortedMulti/MultiA/SortedMultiA)
    /// carry raw key indices, not child Nodes.
    MultiKeys {
        k: u8,
        indices: Vec<u8>,
    },
    /// `tr()` body. Per SPEC v0.30 §7: `is_nums = true` signals the
    /// BIP-341 NUMS H-point as the implicit internal key (key_index unused);
    /// `is_nums = false` references @{key_index} for `key_index < n`.
    Tr {
        is_nums: bool,
        key_index: u8,
        tree: Option<Box<JsonNode>>,
    },
    Hash256Body(String), // hex
    Hash160Body(String), // hex
    Timelock(u32),
    Empty,
}
impl From<&Body> for JsonBody {
    fn from(b: &Body) -> Self {
        match b {
            Body::KeyArg { index } => JsonBody::KeyArg { index: *index },
            Body::Children(v) => JsonBody::Children(v.iter().map(JsonNode::from).collect()),
            Body::Variable { k, children } => JsonBody::Variable {
                k: *k,
                children: children.iter().map(JsonNode::from).collect(),
            },
            Body::MultiKeys { k, indices } => JsonBody::MultiKeys {
                k: *k,
                indices: indices.clone(),
            },
            Body::Tr {
                is_nums,
                key_index,
                tree,
            } => JsonBody::Tr {
                is_nums: *is_nums,
                key_index: *key_index,
                tree: tree.as_ref().map(|n| Box::new(JsonNode::from(n.as_ref()))),
            },
            Body::Hash256Body(h) => JsonBody::Hash256Body(hex(h)),
            Body::Hash160Body(h) => JsonBody::Hash160Body(hex(h)),
            Body::Timelock(v) => JsonBody::Timelock(*v),
            Body::Empty => JsonBody::Empty,
        }
    }
}

#[derive(Serialize, Default)]
pub struct JsonTlv {
    pub use_site_path_overrides: Option<Vec<(u8, JsonUseSitePath)>>,
    pub fingerprints: Option<Vec<(u8, String)>>,
    pub pubkeys: Option<Vec<(u8, String)>>,
    pub origin_path_overrides: Option<Vec<(u8, String)>>,
    pub unknown: Vec<(u8, String, usize)>, // (tag, hex(payload), bit_len)
}
impl From<&TlvSection> for JsonTlv {
    fn from(t: &TlvSection) -> Self {
        Self {
            use_site_path_overrides: t
                .use_site_path_overrides
                .as_ref()
                .map(|v| v.iter().map(|(i, u)| (*i, u.into())).collect()),
            fingerprints: t
                .fingerprints
                .as_ref()
                .map(|v| v.iter().map(|(i, fp)| (*i, hex(fp))).collect()),
            pubkeys: t
                .pubkeys
                .as_ref()
                .map(|v| v.iter().map(|(i, p)| (*i, hex(p))).collect()),
            origin_path_overrides: t.origin_path_overrides.as_ref().map(|v| {
                v.iter()
                    .map(|(i, op)| (*i, format_origin_path(op)))
                    .collect()
            }),
            unknown: t
                .unknown
                .iter()
                .map(|(tag, payload, bits)| (*tag, hex(payload), *bits))
                .collect(),
        }
    }
}

#[cfg(test)]
mod descriptor_json_tests {
    use super::*;
    use crate::parse::template::parse_template;

    #[test]
    fn wsh_multi_serializes() {
        let d = parse_template("wsh(multi(2,@0/<0;1>/*,@1/<0;1>/*))", &[], &[]).unwrap();
        let j = serde_json::to_value(JsonDescriptor::from(&d)).unwrap();
        assert_eq!(j["n"], 2);
        assert_eq!(j["tree"]["tag"], "Wsh");
        assert_eq!(j["tree"]["body"]["kind"], "Children");
        // Inner Multi node:
        assert_eq!(j["tree"]["body"]["data"][0]["tag"], "Multi");
        // v0.30 Phase C: multi-family serializes as MultiKeys with raw indices.
        assert_eq!(j["tree"]["body"]["data"][0]["body"]["kind"], "MultiKeys");
    }

    /// Pin `JsonTag`'s PascalCase contract against serde-rename drift. Each
    /// mixed-case variant is asserted against its expected serialized string
    /// directly — independent of snapshot regen, so a future accidental `_`
    /// insertion or rename-attribute drop fails this test loudly.
    #[test]
    fn json_tag_serialization_pins_pascal_case() {
        let cases: &[(Tag, &str)] = &[
            (Tag::PkK, "PkK"),
            (Tag::PkH, "PkH"),
            (Tag::RawPkH, "RawPkH"),
            (Tag::MultiA, "MultiA"),
            (Tag::SortedMultiA, "SortedMultiA"),
            (Tag::OrI, "OrI"),
            (Tag::DupIf, "DupIf"),
            (Tag::NonZero, "NonZero"),
            (Tag::ZeroNotEqual, "ZeroNotEqual"),
            (Tag::TapTree, "TapTree"),
            (Tag::False, "False"),
            (Tag::True, "True"),
        ];
        for (tag, expected) in cases {
            let json = serde_json::to_value(JsonTag::from(tag)).unwrap();
            assert_eq!(
                json,
                serde_json::Value::String((*expected).to_string()),
                "Tag::{:?} should serialize to {:?}",
                tag,
                expected
            );
        }
    }

    /// `Tag::TapTree` snapshot coverage: no manifest fixture exercises a
    /// branching tap-script-tree (single-leaf `tr()` emits a bare leaf at
    /// root, not a `TapTree` node), so this inline test serves as the live
    /// regression for the `"TapTree"` serialized string. Constructs a minimal
    /// `Body::Tr` with a `TapTree` child and asserts JSON output.
    #[test]
    fn tr_with_taptree_serializes_taptree_string() {
        use md_codec::tree::{Body, Node};
        let leaf = Node {
            tag: Tag::PkK,
            body: Body::KeyArg { index: 1 },
        };
        let tap_branch = Node {
            tag: Tag::TapTree,
            body: Body::Children(vec![leaf.clone(), leaf]),
        };
        let tr = Node {
            tag: Tag::Tr,
            body: Body::Tr {
                is_nums: false,
                key_index: 0,
                tree: Some(Box::new(tap_branch)),
            },
        };
        let j = serde_json::to_value(JsonNode::from(&tr)).unwrap();
        assert_eq!(j["tag"], "Tr");
        let inner_tree = &j["body"]["data"]["tree"];
        assert_eq!(inner_tree["tag"], "TapTree");
        assert_eq!(inner_tree["body"]["kind"], "Children");
    }
}
