use core::ops::{Deref, DerefMut};

use bytemuck::{Pod, Zeroable};
use libk::vfs::Metadata;
use yggdrasil_abi::{
    bitflags,
    io::{FileMode, FileType, GroupId, UserId},
    time::SystemTime,
};

use crate::Ext2Fs;

pub const SUPERBLOCK_OFFSET: u64 = 1024;
pub const EXT2_SIGNATURE: u16 = 0xEF53;
pub const ROOT_INODE: u32 = 2;

pub const DIRECT_BLOCK_COUNT: usize = 12;

bitflags! {
    #[derive(Zeroable, Pod)]
    pub struct FsOptionalFeatures: u32 {
        const PREALLOCATE_DIR_BLOCKS: bit 0;
        const AFS_INODES: bit 1;
        const JOURNAL: bit 2;
        const INODE_EXT_ATTR: bit 3;
        const FS_RESIZE: bit 4;
        const DIR_HASH_INDEX: bit 5;
    }
}

bitflags! {
    #[derive(Zeroable, Pod)]
    pub struct FsRequiredFeatures: u32 {
        const COMPRESSION: bit 0;
        const DIRENT_TYPE_FIELD: bit 1;
        const NEED_RECOVERY: bit 2;
        const SEPARATE_JOURNAL_DEV: bit 3;
        const META_BG: bit 4;
        const EXTENTS: bit 6;
        const FS_64_BIT: bit 7;
        const FLEX_BG: bit 9;
        const EA_INODE: bit 10;
        const DIRDATA: bit 12;
        const CHECKSUM_SEED: bit 13;
        const LARGEDIR: bit 14;
        const INLINE_DATA: bit 15;
        const ENCRYPT: bit 16;
    }
}

bitflags! {
    #[derive(Zeroable, Pod)]
    pub struct FsReadonlyFeatures: u32 {
        const SPARSE_SUPERBLOCKS: bit 0;
        const FILE_SIZE_64_BIT: bit 1;
        const DIR_BTREE: bit 2;
        const SIZE_BLOCKS_NOT_BYTES: bit 3;
        const DESCRIPTOR_CHECKSUM: bit 4;
        const NO_HARD_LINK_LIMIT: bit 5;
        const LARGE_INODES: bit 6;
        const HAS_SNAPSHOT: bit 7;
        const QUOTA: bit 8;
        const CLUSTER_EXTENT_ALLOC: bit 9;
        const METADATA_CHECKSUM: bit 10;
        const REPLICA: bit 11;
        const READ_ONLY: bit 12;
        const PROJECT_QUOTA: bit 13;
    }
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct InodeMode(u16);

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Superblock {
    pub total_inodes: u32,
    pub total_blocks: u32,
    pub root_reserved_blocks: u32,
    pub total_unallocated_blocks: u32,
    pub total_unallocated_inodes: u32,
    pub superblock_number: u32,
    pub block_size_log2: u32,
    pub fragment_size_log2: u32,
    pub block_group_block_count: u32,
    pub block_group_fragment_count: u32,
    pub block_group_inode_count: u32,
    pub last_mount_time: u32,
    pub last_written_time: u32,
    pub mounts_since_fsck: u16,
    pub mounts_allowed_between_fsck: u16,
    pub signature: u16,
    pub state: u16,
    pub error_behavior: u16,
    pub version_minor: u16,
    pub last_fsck_time: u32,
    pub fsck_interval: u32,
    pub creator_os_id: u32,
    pub version_major: u32,
    pub root_user_id: u16,
    pub root_group_id: u16,
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ExtendedSuperblock {
    pub base: Superblock,
    pub first_non_reserved_inode: u32,
    pub inode_struct_size: u16,
    pub superblock_block_group_number: u16,
    pub optional_features: FsOptionalFeatures,
    pub required_features: FsRequiredFeatures,
    pub readonly_features: FsReadonlyFeatures,
    pub filesystem_id: [u8; 16],
    pub volume_name: [u8; 16],
    pub last_mount_path: [u8; 64],
    pub compression_algorithms: u32,
    pub file_prealloc_block_count: u8,
    pub directory_prealloc_block_count: u8,
    _0: u16,
    pub journal_id: [u8; 16],
    pub journal_inode: u32,
    pub journal_device: u32,
    pub orphan_inode_list_head: u32,
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct BlockGroupDescriptor {
    pub block_usage_bitmap: u32,
    pub inode_usage_bitmap: u32,
    pub inode_table: u32,
    pub unallocated_blocks: u16,
    pub unallocated_inodes: u16,
    pub directories: u16,
    _0: [u8; 14],
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Inode {
    pub mode: InodeMode,
    pub uid: u16,
    pub size_lower: u32,
    pub atime: u32,
    pub ctime: u32,
    pub mtime: u32,
    pub dtime: u32,
    pub gid: u16,
    pub hard_links: u16,
    pub sector_count: u32,
    pub flags: u32,
    pub os_val1: u32,
    pub blocks: InodeBlockRefs,
    pub generation: u32,
    pub facl: u32,
    pub size_upper: u32,
    pub frag_block_no: u32,
    pub os_val2: u32,
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct InodeBlockRefs {
    pub direct_blocks: [u32; DIRECT_BLOCK_COUNT],
    pub indirect_block_l1: u32,
    pub indirect_block_l2: u32,
    pub indirect_block_l3: u32,
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Dirent {
    pub ino: u32,
    pub ent_size: u16,
    pub name_length_low: u8,
    pub type_indicator: u8,
}

impl ExtendedSuperblock {
    pub fn inode_size(&self) -> usize {
        if self.base.version_major != 0 {
            self.inode_struct_size as _
        } else {
            128
        }
    }

    pub fn required_features(&self) -> FsRequiredFeatures {
        if self.base.version_major != 0 {
            self.required_features
        } else {
            todo!()
        }
    }

    pub fn readonly_features(&self) -> FsReadonlyFeatures {
        if self.base.version_major != 0 {
            self.readonly_features
        } else {
            todo!()
        }
    }
}

impl Deref for ExtendedSuperblock {
    type Target = Superblock;

    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl DerefMut for ExtendedSuperblock {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

impl Inode {
    pub fn blocks(&self, fs: &Ext2Fs) -> usize {
        self.size(fs).div_ceil(fs.block_size as u64) as usize
    }

    pub fn size(&self, fs: &Ext2Fs) -> u64 {
        let mut size = self.size_lower as u64;
        if fs
            .write_features
            .contains(FsReadonlyFeatures::FILE_SIZE_64_BIT)
        {
            size |= (self.size_upper as u64) << 32;
        }
        size
    }

    pub fn user_id(&self) -> UserId {
        unsafe { UserId::from_raw(self.uid as _) }
    }

    pub fn group_id(&self) -> GroupId {
        unsafe { GroupId::from_raw(self.uid as _) }
    }

    pub fn is_deleted(&self) -> bool {
        self.hard_links == 0
    }

    pub fn metadata(&self, fs: &Ext2Fs, ino: u32) -> Metadata {
        Metadata {
            uid: self.user_id(),
            gid: self.group_id(),
            mode: self.mode.permissions(),
            inode: Some(ino),
            block_count: self.blocks(fs) as _,
            block_size: fs.block_size as _,
            ctime: SystemTime::from_seconds(self.ctime as u64),
            mtime: SystemTime::from_seconds(self.mtime as u64),
            atime: SystemTime::from_seconds(self.atime as u64),
        }
    }
}

impl InodeMode {
    pub fn node_type(&self) -> Option<FileType> {
        match self.0 & 0xF000 {
            0x4000 => Some(FileType::Directory),
            0x8000 => Some(FileType::File),
            0xA000 => Some(FileType::Symlink),
            _ => None,
        }
    }

    pub fn dirent_indicator(&self) -> Option<u8> {
        match self.0 & 0xF000 {
            0x4000 => Some(2),
            0x8000 => Some(1),
            0xA000 => Some(7),
            _ => None,
        }
    }

    pub fn default_for_type(ty: FileType) -> Self {
        match ty {
            FileType::File => Self(0o644 | 0x8000),
            FileType::Directory => Self(0o755 | 0x4000),
            FileType::Symlink => Self(0o755 | 0xA000),
            _ => todo!(),
        }
    }

    pub fn update_permissions(&mut self, mode: FileMode) {
        self.0 &= !0o777;
        self.0 |= (mode.bits() & 0o777) as u16;
    }

    pub fn permissions(&self) -> FileMode {
        unsafe { FileMode::from_raw(self.0 as u32 & 0o777) }
    }
}

impl From<InodeMode> for u16 {
    fn from(value: InodeMode) -> Self {
        value.0
    }
}

impl InodeBlockRefs {
    pub fn empty() -> Self {
        Self {
            direct_blocks: [0; DIRECT_BLOCK_COUNT],
            indirect_block_l1: 0,
            indirect_block_l2: 0,
            indirect_block_l3: 0,
        }
    }
}
