use bitcoin::{
    Script, Witness,
    opcodes::all::OP_IF,
    script::Instruction,
    taproot::{LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE, TAPROOT_LEAF_MASK},
};
use brk_types::OutputType;

use super::{super::features, WitnessFacts};
use crate::TxFeatureFlags;

pub(super) fn analyze<'a>(
    witness: &'a Witness,
    output_type: OutputType,
    flags: &mut TxFeatureFlags,
) -> WitnessFacts<'a> {
    match output_type {
        OutputType::P2TR => analyze_taproot(witness, flags),
        OutputType::P2WPKH => analyze_p2wpkh(witness, flags),
        _ => analyze_ecdsa(witness, flags),
    }
}

fn analyze_p2wpkh<'a>(witness: &'a Witness, flags: &mut TxFeatureFlags) -> WitnessFacts<'a> {
    let mut stack = witness.iter();
    let signature = stack.next().unwrap();
    let public_key = stack.next().unwrap();
    debug_assert!(stack.next().is_none());
    features::record_validated_ecdsa_sighash(signature, flags);

    WitnessFacts {
        has_annex: false,
        last: Some(public_key),
        leaf_version: None,
        max_argument_bytes: signature.len(),
        stack_items: 2,
    }
}

fn analyze_ecdsa<'a>(witness: &'a Witness, flags: &mut TxFeatureFlags) -> WitnessFacts<'a> {
    let stack_items = witness.len();
    let mut last = None;
    let mut max_argument_bytes = 0;

    for (index, item) in witness.iter().enumerate() {
        features::scan_ecdsa_signature(item, flags);
        if index + 1 < stack_items {
            max_argument_bytes = max_argument_bytes.max(item.len());
        }
        last = Some(item);
    }

    WitnessFacts {
        has_annex: false,
        last,
        leaf_version: None,
        max_argument_bytes,
        stack_items,
    }
}

fn analyze_taproot<'a>(witness: &'a Witness, flags: &mut TxFeatureFlags) -> WitnessFacts<'a> {
    let len = witness.len();
    if len == 1 {
        let signature = witness.last().unwrap();
        features::record_validated_taproot_sighash(signature, flags);
        return WitnessFacts {
            has_annex: false,
            last: Some(signature),
            leaf_version: None,
            max_argument_bytes: signature.len(),
            stack_items: 1,
        };
    }

    let last = witness.last();
    let has_annex = len > 1 && last.is_some_and(|item| item.first() == Some(&TAPROOT_ANNEX_PREFIX));
    if has_annex {
        flags.insert(TxFeatureFlags::ANNEX);
    }

    let stack_items = len - usize::from(has_annex);
    let argument_count = if stack_items == 1 {
        1
    } else {
        stack_items.saturating_sub(2)
    };
    let script_index = (stack_items >= 2).then(|| stack_items - 2);
    let control_index = (stack_items >= 2).then(|| stack_items - 1);
    let is_key_path = stack_items == 1;
    let mut leaf_version = None;
    let mut max_argument_bytes = 0;

    for (index, item) in witness.iter().enumerate() {
        if index < argument_count {
            if is_key_path {
                features::record_validated_taproot_sighash(item, flags);
            } else {
                features::scan_taproot_signature(item, flags);
            }
            max_argument_bytes = max_argument_bytes.max(item.len());
        } else if Some(index) == script_index {
            if has_inscription_envelope(Script::from_bytes(item)) {
                flags.insert(TxFeatureFlags::INSCRIPTION);
            }
        } else if Some(index) == control_index && item.len() >= TAPROOT_CONTROL_BASE_SIZE {
            leaf_version = LeafVersion::from_consensus(item[0] & TAPROOT_LEAF_MASK).ok();
        }
    }

    WitnessFacts {
        has_annex,
        last,
        leaf_version,
        max_argument_bytes,
        stack_items,
    }
}

fn has_inscription_envelope(script: &Script) -> bool {
    let mut state = 0;
    for instruction in script.instructions() {
        state = match (state, instruction) {
            (0, Ok(Instruction::PushBytes(bytes))) if bytes.is_empty() => 1,
            (1, Ok(Instruction::Op(OP_IF))) => 2,
            (2, Ok(Instruction::PushBytes(bytes))) if bytes.as_bytes() == b"ord" => return true,
            (_, Ok(Instruction::PushBytes(bytes))) if bytes.is_empty() => 1,
            _ => 0,
        };
    }
    false
}

#[cfg(test)]
mod tests {
    use bitcoin::{ScriptBuf, TxIn, Witness, taproot::TAPROOT_ANNEX_PREFIX};
    use brk_types::OutputType;

    use super::{analyze, has_inscription_envelope};
    use crate::TxFeatureFlags;

    #[test]
    fn recognizes_only_ord_envelopes() {
        let inscription = ScriptBuf::from_hex("0063036f726468").unwrap();
        let generic_envelope = ScriptBuf::from_hex("006303666f6f68").unwrap();

        assert!(has_inscription_envelope(&inscription));
        assert!(!has_inscription_envelope(&generic_envelope));
    }

    #[test]
    fn reads_tapscript_before_control_block_and_annex() {
        let script = ScriptBuf::from_hex("0063036f726468").unwrap();
        let control_block = [0xc0; 33];
        let annex = [TAPROOT_ANNEX_PREFIX, 0x01];
        let input = TxIn {
            witness: Witness::from_slice(&[
                [0x01].as_slice(),
                script.as_bytes(),
                control_block.as_slice(),
                annex.as_slice(),
            ]),
            ..TxIn::default()
        };
        let mut flags = TxFeatureFlags::default();

        let facts = analyze(&input.witness, OutputType::P2TR, &mut flags);

        assert!(facts.has_annex);
        assert!(flags.is_set(TxFeatureFlags::ANNEX));
        assert!(flags.is_set(TxFeatureFlags::INSCRIPTION));
    }

    #[test]
    fn reads_fixed_shape_p2wpkh_once() {
        let signature = [0x01; 71];
        let public_key = [0x02; 33];
        let witness = Witness::from_slice(&[signature.as_slice(), public_key.as_slice()]);
        let mut flags = TxFeatureFlags::default();

        let facts = analyze(&witness, OutputType::P2WPKH, &mut flags);

        assert_eq!(facts.last, Some(public_key.as_slice()));
        assert_eq!(facts.max_argument_bytes, signature.len());
        assert_eq!(facts.stack_items, 2);
        assert!(flags.is_set(TxFeatureFlags::SIGHASH_ALL));
    }

    #[test]
    fn reads_taproot_key_path_once() {
        let signature = [0x00; 64];
        let witness = Witness::from_slice(&[signature]);
        let mut flags = TxFeatureFlags::default();

        let facts = analyze(&witness, OutputType::P2TR, &mut flags);

        assert_eq!(facts.last, Some(signature.as_slice()));
        assert_eq!(facts.max_argument_bytes, signature.len());
        assert_eq!(facts.stack_items, 1);
        assert!(flags.is_set(TxFeatureFlags::SIGHASH_DEFAULT));
    }
}
