use std::fmt::{Debug, Display, Formatter};
use std::num::NonZeroU32;

use tycho_network::PeerId;

use crate::effects::{AltFmt, AltFormat};
use crate::models::{Digest, PointId, PointInfo, Round};
use crate::storage::{AnchorFlags, StatusFlags};

pub enum PointRestoreSelect {
    /// Note: currently all points with `Exists` statuses must repeat `verify()`,
    ///       as this status also represents an error during merge of DB statuses;
    ///       also config or `v_set` may have changed after reboot if node lagged too far
    NeedsVerify(Round, Digest),
    Ready(PointRestore),
}
pub enum PointRestore {
    /// non-terminal status can be found in DB if point task was aborted before validation
    Exists(PointInfo),
    Validated(PointInfo, PointStatusValidated),
    IllFormed(PointId, PointStatusIllFormed),
    NotFound(Round, Digest, PointStatusNotFound),
}
impl PointRestore {
    /// required partial order: resolved + valid, resolved, valid, no flags, no status
    pub fn restore_order_asc(&self) -> u8 {
        /// greater value for greater priority
        fn order_desc<T: PointStatus>(status: &T) -> u8 {
            let mut priority = 1;
            priority |= (status.is_first_resolved() as u8) << 7;
            priority |= (status.is_first_valid() as u8) << 6;
            priority |= (status.is_valid() as u8) << 5;
            priority
        }
        let desc = match self {
            PointRestore::Exists(_) => 0,
            PointRestore::Validated(_, status) => order_desc(status),
            PointRestore::IllFormed(_, status) => order_desc(status),
            PointRestore::NotFound(_, _, status) => order_desc(status),
        };
        !desc // invert priority for ascending order
    }
    pub fn round(&self) -> Round {
        match self {
            Self::Exists(info) | Self::Validated(info, _) => info.round(),
            Self::IllFormed(id, _) => id.round,
            Self::NotFound(round, _, _) => *round,
        }
    }
    pub fn author(&self) -> &PeerId {
        match self {
            Self::Exists(info) | Self::Validated(info, _) => info.author(),
            Self::IllFormed(id, _) => &id.author,
            Self::NotFound(_, _, status) => &status.author,
        }
    }
    pub fn digest(&self) -> &Digest {
        match self {
            Self::Exists(info) | Self::Validated(info, _) => info.digest(),
            Self::IllFormed(id, _) => &id.digest,
            Self::NotFound(_, digest, _) => digest,
        }
    }
    pub fn id(&self) -> PointId {
        PointId {
            author: *self.author(),
            round: self.round(),
            digest: *self.digest(),
        }
    }
}

impl AltFormat for PointRestore {}
impl Debug for AltFmt<'_, PointRestore> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let inner = AltFormat::unpack(self);
        write!(f, "Restore {{ {:?} ", inner.id().alt())?;
        match inner {
            PointRestore::Exists(_) => {
                write!(f, "Exists")?;
            }
            PointRestore::Validated(_, status) => {
                write!(f, "{status}")?;
            }
            PointRestore::IllFormed(_, status) => {
                write!(f, "{status}")?;
            }
            PointRestore::NotFound(_, _, status) => {
                let mut tuple = f.debug_tuple("NotFound");
                if status.is_first_resolved {
                    tuple.field(&"first resolved");
                }
                if status.is_certified {
                    tuple.field(&"certified");
                }
                // author is shown in point id
                tuple.finish()?;
            }
        }
        f.write_str(" }")
    }
}

/// To read from DB
#[derive(Debug)]
pub enum PointStatusStored {
    Exists,
    Validated(PointStatusValidated),
    IllFormed(PointStatusIllFormed),
    NotFound(PointStatusNotFound),
}
impl Display for PointStatusStored {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.as_ref(), f)
    }
}

/// To pass `impl PointStatus` to DB that must not implement Clone.
pub enum PointStatusStoredRef<'a> {
    Exists,
    Validated(&'a PointStatusValidated),
    IllFormed(&'a PointStatusIllFormed),
    NotFound(&'a PointStatusNotFound),
}
impl Display for PointStatusStoredRef<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Exists => f.write_str("Exists"),
            Self::Validated(resolved) => Display::fmt(resolved, f),
            Self::IllFormed(resolved) => Display::fmt(resolved, f),
            Self::NotFound(resolved) => Display::fmt(resolved, f),
        }
    }
}

pub trait PointStatus: Display {
    fn is_valid(&self) -> bool {
        false
    }
    fn set_first_valid(&mut self) {}
    fn is_first_valid(&self) -> bool {
        false
    }
    fn set_first_resolved(&mut self);
    fn is_first_resolved(&self) -> bool;
    fn size_hint() -> usize;
    fn write_to(&self, buffer: &mut Vec<u8>);

    fn encode(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(Self::size_hint());
        self.write_to(&mut buf);
        buf
    }
}

/// Must not implement neither Copy nor Clone to prevent coding errors.
#[derive(Debug, Default)]
pub struct PointStatusValidated {
    pub is_valid: bool,
    pub is_first_valid: bool,
    pub is_first_resolved: bool,
    pub is_certified: bool,
    pub anchor_flags: AnchorFlags,
    pub committed: Option<CommitHistoryPart>,
}
/// Not committed are stored with impossible zero round
#[derive(Debug)]
pub struct CommitHistoryPart {
    pub anchor_round: NonZeroU32,
    pub seq_no: u32,
}
impl Display for PointStatusValidated {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut tuple = if self.is_valid {
            f.debug_tuple("Valid")
        } else {
            f.debug_tuple("Invalid")
        };
        if self.is_first_valid {
            tuple.field(&"first valid");
        }
        if self.is_first_resolved {
            tuple.field(&"first resolved");
        }
        if self.is_certified {
            tuple.field(&"certified");
        }
        tuple.finish_non_exhaustive()
    }
}
impl PointStatus for PointStatusValidated {
    fn is_valid(&self) -> bool {
        self.is_valid
    }
    fn set_first_valid(&mut self) {
        self.is_first_valid = true;
    }
    fn is_first_valid(&self) -> bool {
        self.is_first_valid
    }
    fn set_first_resolved(&mut self) {
        self.is_first_resolved = true;
    }
    fn is_first_resolved(&self) -> bool {
        self.is_first_resolved
    }
    fn size_hint() -> usize {
        StatusFlags::VALIDATED_BYTES
    }
    fn write_to(&self, buffer: &mut Vec<u8>) {
        let mut flags = StatusFlags::empty();
        flags.insert(StatusFlags::Found);
        flags.insert(StatusFlags::WellFormed);
        flags.set(StatusFlags::Valid, self.is_valid);
        flags.set(StatusFlags::FirstValid, self.is_first_valid);
        flags.set(StatusFlags::FirstResolved, self.is_first_resolved);
        flags.set(StatusFlags::Certified, self.is_certified);

        buffer.push(flags.bits());
        buffer.push(self.anchor_flags.bits());
        CommitHistoryPart::write(self.committed.as_ref(), buffer);
    }
}

/// Must not implement neither Copy nor Clone to prevent coding errors.
#[derive(Debug, Default)]
pub struct PointStatusIllFormed {
    pub is_first_resolved: bool,
    pub is_certified: bool,
}
impl Display for PointStatusIllFormed {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut tuple = f.debug_tuple("IllFormed");
        if self.is_first_resolved {
            tuple.field(&"first resolved");
        }
        if self.is_certified {
            tuple.field(&"certified");
        }
        tuple.finish()
    }
}
impl PointStatus for PointStatusIllFormed {
    fn set_first_resolved(&mut self) {
        self.is_first_resolved = true;
    }
    fn is_first_resolved(&self) -> bool {
        self.is_first_resolved
    }
    fn size_hint() -> usize {
        StatusFlags::ILL_FORMED_BYTES
    }
    fn write_to(&self, buffer: &mut Vec<u8>) {
        let mut flags = StatusFlags::empty();
        flags.insert(StatusFlags::Found);
        flags.set(StatusFlags::FirstResolved, self.is_first_resolved);
        flags.set(StatusFlags::Certified, self.is_certified);

        buffer.push(flags.bits());
    }
}

/// Must not implement neither Copy nor Clone to prevent coding errors.
#[derive(Debug)]
pub struct PointStatusNotFound {
    pub is_first_resolved: bool,
    pub is_certified: bool,
    pub author: PeerId,
}
impl Display for PointStatusNotFound {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut tuple = f.debug_tuple("NotFound");
        if self.is_first_resolved {
            tuple.field(&"first resolved");
        }
        if self.is_certified {
            tuple.field(&"certified");
        }
        tuple.field(&format!("author: {}", self.author.alt()));
        tuple.finish()
    }
}

impl PointStatus for PointStatusNotFound {
    fn set_first_resolved(&mut self) {
        self.is_first_resolved = true;
    }
    fn is_first_resolved(&self) -> bool {
        self.is_first_resolved
    }
    fn size_hint() -> usize {
        StatusFlags::NOT_FOUND_BYTES
    }
    fn write_to(&self, buffer: &mut Vec<u8>) {
        let mut flags = StatusFlags::empty();
        flags.set(StatusFlags::FirstResolved, self.is_first_resolved);
        flags.set(StatusFlags::Certified, self.is_certified);

        buffer.push(flags.bits());
        buffer.extend_from_slice(&self.author.0);
    }
}

impl PointStatusStoredRef<'_> {
    pub fn encode(&self) -> Vec<u8> {
        match self {
            Self::Exists => Vec::new(), // no data
            Self::Validated(resolved) => resolved.encode(),
            Self::IllFormed(resolved) => resolved.encode(),
            Self::NotFound(resolved) => resolved.encode(),
        }
    }

    pub fn write_to(&self, buffer: &mut Vec<u8>) {
        match self {
            Self::Exists => {} // no data
            Self::Validated(resolved) => resolved.write_to(buffer),
            Self::IllFormed(resolved) => resolved.write_to(buffer),
            Self::NotFound(resolved) => resolved.write_to(buffer),
        }
    }
}

impl PointStatusStored {
    pub fn as_ref(&self) -> PointStatusStoredRef<'_> {
        match self {
            Self::Exists => PointStatusStoredRef::Exists,
            Self::Validated(resolved) => PointStatusStoredRef::Validated(resolved),
            Self::IllFormed(resolved) => PointStatusStoredRef::IllFormed(resolved),
            Self::NotFound(resolved) => PointStatusStoredRef::NotFound(resolved),
        }
    }
    pub fn decode(stored: &[u8]) -> anyhow::Result<Self> {
        let Some(flags) = StatusFlags::try_from_stored(stored)? else {
            return Ok(Self::Exists);
        };
        let resolved = if flags.contains(StatusFlags::Found) {
            if flags.contains(StatusFlags::WellFormed) {
                Self::Validated(PointStatusValidated {
                    is_valid: flags.contains(StatusFlags::Valid),
                    is_first_valid: flags.contains(StatusFlags::FirstValid),
                    is_first_resolved: flags.contains(StatusFlags::FirstResolved),
                    is_certified: flags.contains(StatusFlags::Certified),
                    anchor_flags: AnchorFlags::from_bits_retain(stored[1]),
                    committed: CommitHistoryPart::read(&stored[2..]),
                })
            } else {
                Self::IllFormed(PointStatusIllFormed {
                    is_first_resolved: flags.contains(StatusFlags::FirstResolved),
                    is_certified: flags.contains(StatusFlags::Certified),
                })
            }
        } else {
            let mut author = [0_u8; 32];
            author.copy_from_slice(&stored[1..]);
            Self::NotFound(PointStatusNotFound {
                author: PeerId(author),
                is_first_resolved: flags.contains(StatusFlags::FirstResolved),
                is_certified: flags.contains(StatusFlags::Certified),
            })
        };
        Ok(resolved)
    }
}
impl CommitHistoryPart {
    fn write(this: Option<&Self>, dest: &mut Vec<u8>) {
        let anchor_round = this.map(|c| c.anchor_round.get()).unwrap_or_default();
        let seq_no = this.map(|c| c.seq_no).unwrap_or_default();

        dest.extend_from_slice(&anchor_round.to_be_bytes());
        dest.extend_from_slice(&seq_no.to_be_bytes());
    }
    fn read(slice: &[u8]) -> Option<Self> {
        assert_eq!(slice.len(), 4 + 4, "slice len must be 8 bytes");
        let mut u32_buf = [0_u8; 4];

        u32_buf.copy_from_slice(&slice[..4]);
        let anchor_round = NonZeroU32::new(u32::from_be_bytes(u32_buf))?;

        u32_buf.copy_from_slice(&slice[4..8]);
        let seq_no = u32::from_be_bytes(u32_buf);

        Some(Self {
            anchor_round,
            seq_no,
        })
    }
}
