use std::iter;
use secp256k1::{ecdsa, schnorr};
use crate::{NonStandardValue, LIB_NAME_BITCOIN};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[repr(u8)]
pub enum SighashFlag {
    #[default]
    All = 0x01,
    None = 0x02,
    Single = 0x03,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct SighashType {
    pub flag: SighashFlag,
    pub anyone_can_pay: bool,
}
impl SighashType {
    pub const fn all() -> Self {
        SighashType {
            flag: SighashFlag::All,
            anyone_can_pay: false,
        }
    }
    pub const fn none() -> Self {
        SighashType {
            flag: SighashFlag::None,
            anyone_can_pay: false,
        }
    }
    pub const fn single() -> Self {
        SighashType {
            flag: SighashFlag::Single,
            anyone_can_pay: false,
        }
    }
    pub const fn all_anyone_can_pay() -> Self {
        SighashType {
            flag: SighashFlag::All,
            anyone_can_pay: true,
        }
    }
    pub const fn none_anyone_can_pay() -> Self {
        SighashType {
            flag: SighashFlag::None,
            anyone_can_pay: true,
        }
    }
    pub const fn single_anyone_can_pay() -> Self {
        SighashType {
            flag: SighashFlag::Single,
            anyone_can_pay: true,
        }
    }
    pub fn from_consensus_u32(n: u32) -> SighashType {
        let mask = 0x1f | 0x80;
        let (flag, anyone_can_pay) = match n & mask {
            0x01 => (SighashFlag::All, false),
            0x02 => (SighashFlag::None, false),
            0x03 => (SighashFlag::Single, false),
            0x81 => (SighashFlag::All, true),
            0x82 => (SighashFlag::None, true),
            0x83 => (SighashFlag::Single, true),
            x if x & 0x80 == 0x80 => (SighashFlag::All, true),
            _ => (SighashFlag::All, false),
        };
        SighashType {
            flag,
            anyone_can_pay,
        }
    }
    pub fn from_standard_u32(n: u32) -> Result<SighashType, NonStandardValue<u32>> {
        let (flag, anyone_can_pay) = match n {
            0x01 => (SighashFlag::All, false),
            0x02 => (SighashFlag::None, false),
            0x03 => (SighashFlag::Single, false),
            0x81 => (SighashFlag::All, true),
            0x82 => (SighashFlag::None, true),
            0x83 => (SighashFlag::Single, true),
            non_standard => return Err(NonStandardValue::with(non_standard, "SighashType")),
        };
        Ok(SighashType {
            flag,
            anyone_can_pay,
        })
    }
    #[inline]
    pub const fn into_consensus_u32(self) -> u32 { self.into_consensus_u8() as u32 }
    #[inline]
    pub const fn to_consensus_u32(&self) -> u32 { self.into_consensus_u32() }
    pub const fn into_consensus_u8(self) -> u8 {
        let flag = self.flag as u8;
        let mask = (self.anyone_can_pay as u8) << 7;
        flag | mask
    }
    pub const fn to_consensus_u8(self) -> u8 {
        let flag = self.flag as u8;
        let mask = (self.anyone_can_pay as u8) << 7;
        flag | mask
    }
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum SigError {
    #[display(inner)]
    #[from]
    SighashType(NonStandardValue<u32>),
    EmptySignature,
    DerEncoding,
    Bip340Encoding(usize),
    InvalidSignature,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[derive(StrictType)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct LegacySig {
    pub sig: ecdsa::Signature,
    pub sighash_type: SighashType,
}
impl LegacySig {
    pub fn sighash_all(sig: ecdsa::Signature) -> LegacySig {
        LegacySig {
            sig,
            sighash_type: SighashType::all(),
        }
    }
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SigError> {
        let (hash_ty, sig) = bytes.split_last().ok_or(SigError::EmptySignature)?;
        let sighash_type = SighashType::from_standard_u32(*hash_ty as u32)?;
        let sig = ecdsa::Signature::from_der(sig).map_err(|_| SigError::DerEncoding)?;
        Ok(LegacySig { sig, sighash_type })
    }
    pub fn to_vec(self) -> Vec<u8> {
        self.sig
            .serialize_der()
            .iter()
            .copied()
            .chain(iter::once(self.sighash_type.into_consensus_u8()))
            .collect()
    }
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[derive(StrictType)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Bip340Sig {
    pub sig: schnorr::Signature,
    pub sighash_type: Option<SighashType>,
}
impl Bip340Sig {
    pub fn sighash_default(sig: schnorr::Signature) -> Self {
        Bip340Sig {
            sig,
            sighash_type: None,
        }
    }
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SigError> {
        let (hash_ty, sig) = match bytes.len() {
            0 => return Err(SigError::EmptySignature),
            64 => (None, bytes),
            65 => (Some(bytes[64] as u32), &bytes[..64]),
            invalid => return Err(SigError::Bip340Encoding(invalid)),
        };
        let sighash_type = hash_ty.map(SighashType::from_standard_u32).transpose()?;
        let sig = schnorr::Signature::from_slice(sig).map_err(|_| SigError::InvalidSignature)?;
        Ok(Bip340Sig { sig, sighash_type })
    }
    pub fn to_vec(self) -> Vec<u8> {
        let mut ser = Vec::<u8>::with_capacity(65);
        ser.extend_from_slice(&self.sig[..]);
        if let Some(sighash_type) = self.sighash_type {
            ser.push(sighash_type.into_consensus_u8())
        }
        ser
    }
}
mod _strict_encode {
    use std::io;
    use amplify::confinement::TinyBlob;
    use amplify::hex::FromHex;
    use amplify::Bytes64;
    use strict_encoding::{
        DecodeError, ReadStruct, StrictDecode, StrictDumb, StrictEncode, TypedRead, TypedWrite,
        WriteStruct,
    };
    use super::*;
    impl StrictDumb for LegacySig {
        fn strict_dumb() -> Self {
            Self {
                sig: ecdsa::Signature::from_der(&Vec::<u8>::from_hex(
                    "304402206fa6c164fb89906e2e1d291cc5461ceadf0f115c6b71e58f87482c94d512c3630220\
                    0ab641f3ece1d77f13ad2d8910cb7abd5a9b85f0f9036317dbb1470f22e7714c").unwrap()
                ).expect("hardcoded signature"),
                sighash_type: default!(),
            }
        }
    }
    impl StrictEncode for LegacySig {
        fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
            writer.write_struct::<Self>(|w| {
                Ok(w.write_field(
                    fname!("sig"),
                    &TinyBlob::try_from(self.sig.serialize_der().to_vec())
                        .expect("invalid signature"),
                )?
                .write_field(fname!("sighash_type"), &self.sighash_type)?
                .complete())
            })
        }
    }
    impl StrictDecode for LegacySig {
        fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
            reader.read_struct(|r| {
                let bytes: TinyBlob = r.read_field(fname!("sig"))?;
                let sig = ecdsa::Signature::from_der(bytes.as_slice()).map_err(|_| {
                    DecodeError::DataIntegrityError(s!("invalid signature DER encoding"))
                })?;
                let sighash_type = r.read_field(fname!("sighash_type"))?;
                Ok(Self { sig, sighash_type })
            })
        }
    }
    impl StrictDumb for Bip340Sig {
        fn strict_dumb() -> Self {
            Bip340Sig::from_bytes(&Vec::<u8>::from_hex(
                "a12b3f4c224619d7834f0bad0a598b79111ba08146ae1205f3e6220a132aef0ed8290379624db643\
                e6b861d8dcd37b406a11f91a51bf5a6cdf9b3c9b772f67c301"
            ).unwrap())
            .expect("hardcoded signature")
        }
    }
    impl StrictEncode for Bip340Sig {
        fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
            writer.write_struct::<Self>(|w| {
                Ok(w.write_field(fname!("sig"), &Bytes64::from(*self.sig.as_ref()))?
                    .write_field(fname!("sighash_type"), &self.sighash_type)?
                    .complete())
            })
        }
    }
    impl StrictDecode for Bip340Sig {
        fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
            reader.read_struct(|r| {
                let bytes: Bytes64 = r.read_field(fname!("sig"))?;
                let sig = schnorr::Signature::from_slice(bytes.as_slice()).map_err(|_| {
                    DecodeError::DataIntegrityError(format!(
                        "invalid signature BIP340 encoding '{bytes:x}'"
                    ))
                })?;
                let sighash_type = r.read_field(fname!("sighash_type"))?;
                Ok(Self { sig, sighash_type })
            })
        }
    }
}