use serde::{Serialize, Deserialize};
use std::fmt;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attribute {
    pub tag: String,
    pub value: String,
    pub position: crate::geometry::Point,
    pub text_style: crate::text::TextStyle,
    pub is_invisible: bool,
    pub is_constant: bool,
    pub is_verify: bool,
    pub is_locked: bool,
    pub alignment: crate::text::TextAlignment,
    pub height: f64,
    pub rotation: f64,
    pub width_factor: f64,
    pub oblique_angle: f64,
}

impl Default for Attribute {
    fn default() -> Self {
        Self {
            tag: String::new(),
            value: String::new(),
            position: crate::geometry::Point::origin(),
            text_style: crate::text::TextStyle::default(),
            is_invisible: false,
            is_constant: false,
            is_verify: false,
            is_locked: false,
            alignment: crate::text::TextAlignment::Left,
            height: 2.5,
            rotation: 0.0,
            width_factor: 1.0,
            oblique_angle: 0.0,
        }
    }
}

impl Attribute {
    #[inline]
    pub fn new(tag: &str, value: &str) -> Self {
        Self {
            tag: tag.to_string(),
            value: value.to_string(),
            ..Default::default()
        }
    }

    #[inline]
    pub fn with_position(mut self, position: crate::geometry::Point) -> Self {
        self.position = position;
        self
    }

    #[inline]
    pub fn set_tag(&mut self, tag: &str) {
        self.tag = tag.to_string();
    }

    #[inline]
    pub fn set_value(&mut self, value: &str) {
        self.value = value.to_string();
    }

    #[inline]
    pub fn is_visible(&self) -> bool {
        !self.is_invisible
    }
}

impl fmt::Display for Attribute {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}={}", self.tag, self.value)
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AttributeDefinition {
    pub tag: String,
    pub prompt: String,
    pub default_value: String,
    pub position: crate::geometry::Point,
    pub text_style: crate::text::TextStyle,
    pub field_length: u32,
    pub alignment: crate::text::TextAlignment,
    pub is_invisible: bool,
    pub is_constant: bool,
    pub is_verify: bool,
    pub is_locked: bool,
    pub height: f64,
    pub rotation: f64,
    pub width_factor: f64,
    pub oblique_angle: f64,
    pub mtext_bottom: bool,
}

impl Default for AttributeDefinition {
    fn default() -> Self {
        Self {
            tag: String::new(),
            prompt: String::new(),
            default_value: String::new(),
            position: crate::geometry::Point::origin(),
            text_style: crate::text::TextStyle::default(),
            field_length: 0,
            alignment: crate::text::TextAlignment::Left,
            is_invisible: false,
            is_constant: false,
            is_verify: false,
            is_locked: false,
            height: 2.5,
            rotation: 0.0,
            width_factor: 1.0,
            oblique_angle: 0.0,
            mtext_bottom: false,
        }
    }
}

impl AttributeDefinition {
    #[inline]
    pub fn new(tag: &str, prompt: &str) -> Self {
        Self {
            tag: tag.to_string(),
            prompt: prompt.to_string(),
            ..Default::default()
        }
    }

    #[inline]
    pub fn with_defaults(tag: &str, prompt: &str, position: crate::geometry::Point) -> Self {
        Self {
            tag: tag.to_string(),
            prompt: prompt.to_string(),
            position,
            ..Default::default()
        }
    }

    #[inline]
    pub fn create_attribute(&self, value: &str) -> Attribute {
        Attribute {
            tag: self.tag.clone(),
            value: value.to_string(),
            position: self.position,
            text_style: self.text_style.clone(),
            is_invisible: self.is_invisible,
            is_constant: self.is_constant,
            is_verify: self.is_verify,
            is_locked: self.is_locked,
            alignment: self.alignment,
            height: self.height,
            rotation: self.rotation,
            width_factor: self.width_factor,
            oblique_angle: self.oblique_angle,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockTableRecord {
    pub name: String,
    pub objects: Vec<super::data_structure::ObjectId>,
    pub attribute_defs: Vec<AttributeDefinition>,
    pub origin: crate::geometry::Point,
    pub units: DrawingUnit,
    pub scaling: (f64, f64),
    pub description: String,
    pub is_explodable: bool,
    pub block_unit: BlockUnit,
    pub comments: String,
}

impl Default for BlockTableRecord {
    fn default() -> Self {
        Self::new("*Unnamed")
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DrawingUnit {
    Unitless,
    Inches,
    Feet,
    Miles,
    Millimeters,
    Centimeters,
    Meters,
    Kilometers,
    Microinches,
    Mils,
    Angstroms,
    Nanometers,
    Microns,
    Decimeters,
    Decameters,
    Hectometers,
    Gigameters,
    AstronomicalUnits,
    LightYears,
    Parsecs,
}

impl Default for DrawingUnit {
    fn default() -> Self {
        DrawingUnit::Unitless
    }
}

impl DrawingUnit {
    #[inline]
    pub fn conversion_factor(&self) -> f64 {
        match self {
            DrawingUnit::Unitless => 1.0,
            DrawingUnit::Inches => 25.4,
            DrawingUnit::Feet => 304.8,
            DrawingUnit::Miles => 1609344.0,
            DrawingUnit::Millimeters => 1.0,
            DrawingUnit::Centimeters => 10.0,
            DrawingUnit::Meters => 1000.0,
            DrawingUnit::Kilometers => 1000000.0,
            DrawingUnit::Microinches => 0.0000254,
            DrawingUnit::Mils => 0.0254,
            DrawingUnit::Angstroms => 0.0000001,
            DrawingUnit::Nanometers => 0.000001,
            DrawingUnit::Microns => 0.001,
            DrawingUnit::Decimeters => 100.0,
            DrawingUnit::Decameters => 10000.0,
            DrawingUnit::Hectometers => 100000.0,
            DrawingUnit::Gigameters => 1000000000.0,
            DrawingUnit::AstronomicalUnits => 149597870700000.0,
            DrawingUnit::LightYears => 9460730472580800000.0,
            DrawingUnit::Parsecs => 30856775814913673000.0,
        }
    }

    #[inline]
    pub fn to_millimeters(&self, value: f64) -> f64 {
        value * self.conversion_factor()
    }

    #[inline]
    pub fn from_millimeters(&self, value: f64) -> f64 {
        value / self.conversion_factor()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BlockUnit {
    Unitless,
    Inches,
    Feet,
    Millimeters,
    Centimeters,
    Meters,
}

impl Default for BlockUnit {
    fn default() -> Self {
        BlockUnit::Unitless
    }
}

impl BlockUnit {
    #[inline]
    pub fn from_drawing_unit(unit: DrawingUnit) -> Self {
        match unit {
            DrawingUnit::Unitless => BlockUnit::Unitless,
            DrawingUnit::Inches | DrawingUnit::Feet | DrawingUnit::Miles => BlockUnit::Inches,
            DrawingUnit::Millimeters => BlockUnit::Millimeters,
            DrawingUnit::Centimeters => BlockUnit::Centimeters,
            DrawingUnit::Meters => BlockUnit::Meters,
            _ => BlockUnit::Unitless,
        }
    }
}

impl BlockTableRecord {
    #[inline]
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            objects: Vec::new(),
            attribute_defs: Vec::new(),
            origin: crate::geometry::Point::origin(),
            units: DrawingUnit::Unitless,
            scaling: (1.0, 1.0),
            description: String::new(),
            is_explodable: true,
            block_unit: BlockUnit::default(),
            comments: String::new(),
        }
    }

    #[inline]
    pub fn add_object(&mut self, object_id: super::data_structure::ObjectId) {
        if !self.objects.contains(&object_id) {
            self.objects.push(object_id);
        }
    }

    #[inline]
    pub fn remove_object(&mut self, object_id: &super::data_structure::ObjectId) {
        self.objects.retain(|id| id != object_id);
    }

    #[inline]
    pub fn add_attribute_def(&mut self, attr_def: AttributeDefinition) {
        self.attribute_defs.push(attr_def);
    }

    #[inline]
    pub fn remove_attribute_def(&mut self, tag: &str) {
        self.attribute_defs.retain(|def| def.tag != tag);
    }

    #[inline]
    pub fn get_attribute_def(&self, tag: &str) -> Option<&AttributeDefinition> {
        self.attribute_defs.iter().find(|def| def.tag == tag)
    }

    #[inline]
    pub fn object_count(&self) -> usize {
        self.objects.len()
    }

    #[inline]
    pub fn attribute_count(&self) -> usize {
        self.attribute_defs.len()
    }

    #[inline]
    pub fn clear(&mut self) {
        self.objects.clear();
        self.attribute_defs.clear();
    }
}

#[derive(Debug, Clone)]
pub struct BlockTable {
    records: std::collections::HashMap<String, BlockTableRecord>,
    current_block: Option<String>,
}

impl Default for BlockTable {
    fn default() -> Self {
        Self::new()
    }
}

impl BlockTable {
    #[inline]
    pub fn new() -> Self {
        let mut table = Self {
            records: std::collections::HashMap::new(),
            current_block: None,
        };
        table.register_builtin_blocks();
        table
    }

    fn register_builtin_blocks(&mut self) {
        self.records.insert("*Model_Space".to_string(), BlockTableRecord::new("*Model_Space"));
        self.records.insert("*Paper_Space".to_string(), BlockTableRecord::new("*Paper_Space"));
        self.records.insert("*Paper_Space0".to_string(), BlockTableRecord::new("*Paper_Space0"));
    }

    #[inline]
    pub fn register(&mut self, record: BlockTableRecord) -> bool {
        if record.name.is_empty() {
            return false;
        }
        self.records.insert(record.name.clone(), record);
        true
    }

    #[inline]
    pub fn get(&self, name: &str) -> Option<&BlockTableRecord> {
        self.records.get(name)
    }

    #[inline]
    pub fn get_mut(&mut self, name: &str) -> Option<&mut BlockTableRecord> {
        self.records.get_mut(name)
    }

    #[inline]
    pub fn has(&self, name: &str) -> bool {
        self.records.contains_key(name)
    }

    #[inline]
    pub fn remove(&mut self, name: &str) -> bool {
        if name.starts_with('*') {
            return false;
        }
        self.records.remove(name).is_some()
    }

    #[inline]
    pub fn rename(&mut self, old_name: &str, new_name: &str) -> bool {
        if old_name.starts_with('*') || new_name.starts_with('*') {
            return false;
        }
        if let Some(record) = self.records.remove(old_name) {
            let mut new_record = record;
            new_record.name = new_name.to_string();
            self.records.insert(new_name.to_string(), new_record);
            true
        } else {
            false
        }
    }

    #[inline]
    pub fn set_current(&mut self, name: &str) -> bool {
        if self.records.contains_key(name) {
            self.current_block = Some(name.to_string());
            true
        } else {
            false
        }
    }

    #[inline]
    pub fn current(&self) -> Option<&str> {
        self.current_block.as_deref()
    }

    #[inline]
    pub fn current_mut(&mut self) -> Option<&mut BlockTableRecord> {
        if let Some(ref name) = self.current_block {
            self.records.get_mut(name)
        } else {
            None
        }
    }

    #[inline]
    pub fn names(&self) -> Vec<&str> {
        self.records.keys().map(|s| s.as_str()).collect()
    }

    #[inline]
    pub fn count(&self) -> usize {
        self.records.len()
    }

    #[inline]
    pub fn clear(&mut self) {
        self.records.clear();
        self.current_block = None;
        self.register_builtin_blocks();
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InsertEntity {
    pub block_name: String,
    pub position: crate::geometry::Point,
    pub scale: (f64, f64, f64),
    pub rotation: f64,
    pub columns: u32,
    pub rows: u32,
    pub column_spacing: f64,
    pub row_spacing: f64,
    pub attributes: Vec<Attribute>,
}

impl Default for InsertEntity {
    fn default() -> Self {
        Self {
            block_name: String::new(),
            position: crate::geometry::Point::origin(),
            scale: (1.0, 1.0, 1.0),
            rotation: 0.0,
            columns: 1,
            rows: 1,
            column_spacing: 0.0,
            row_spacing: 0.0,
            attributes: Vec::new(),
        }
    }
}

impl InsertEntity {
    #[inline]
    pub fn new(block_name: &str) -> Self {
        Self {
            block_name: block_name.to_string(),
            ..Default::default()
        }
    }

    #[inline]
    pub fn with_transform(mut self, position: crate::geometry::Point, scale: (f64, f64, f64), rotation: f64) -> Self {
        self.position = position;
        self.scale = scale;
        self.rotation = rotation;
        self
    }

    #[inline]
    pub fn add_attribute(&mut self, attribute: Attribute) {
        self.attributes.push(attribute);
    }

    #[inline]
    pub fn set_attribute(&mut self, tag: &str, value: &str) {
        if let Some(attr) = self.attributes.iter_mut().find(|a| a.tag == tag) {
            attr.value = value.to_string();
        }
    }

    #[inline]
    pub fn get_attribute(&self, tag: &str) -> Option<&Attribute> {
        self.attributes.iter().find(|a| a.tag == tag)
    }

    #[inline]
    pub fn attribute_count(&self) -> usize {
        self.attributes.len()
    }

    #[inline]
    pub fn create_array(&self, columns: u32, rows: u32, col_spacing: f64, row_spacing: f64) -> Vec<InsertEntity> {
        let mut array = Vec::new();
        for row in 0..rows {
            for col in 0..columns {
                if row == 0 && col == 0 {
                    array.push(self.clone());
                } else {
                    let offset_x = col as f64 * col_spacing;
                    let offset_y = row as f64 * row_spacing;
                    let new_position = crate::geometry::Point::new(
                        self.position.x + offset_x,
                        self.position.y + offset_y,
                        self.position.z,
                    );
                    let mut new_insert = self.clone();
                    new_insert.position = new_position;
                    array.push(new_insert);
                }
            }
        }
        array
    }

    #[inline]
    pub fn transformation_matrix(&self) -> crate::math::Matrix4 {
        let cos_r = self.rotation.cos();
        let sin_r = self.rotation.sin();
        let (sx, sy, sz) = self.scale;

        crate::math::Matrix4::new(
            sx * cos_r, sy * -sin_r, 0.0, self.position.x,
            sx * sin_r, sy * cos_r, 0.0, self.position.y,
            0.0, 0.0, sz, self.position.z,
            0.0, 0.0, 0.0, 1.0,
        )
    }
}

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

    #[test]
    fn test_attribute_creation() {
        let attr = Attribute::new("TAG1", "Value1");
        assert_eq!(attr.tag, "TAG1");
        assert_eq!(attr.value, "Value1");
    }

    #[test]
    fn test_attribute_definition() {
        let def = AttributeDefinition::new("TAG1", "Enter value:");
        assert_eq!(def.tag, "TAG1");
        assert_eq!(def.prompt, "Enter value:");
    }

    #[test]
    fn test_attribute_definition_create_attribute() {
        let def = AttributeDefinition::new("TAG1", "Enter value:");
        let attr = def.create_attribute("TestValue");
        assert_eq!(attr.tag, "TAG1");
        assert_eq!(attr.value, "TestValue");
    }

    #[test]
    fn test_block_table_record() {
        let mut record = BlockTableRecord::new("TestBlock");
        assert_eq!(record.name, "TestBlock");
        assert!(record.object_count() == 0);
    }

    #[test]
    fn test_block_table_record_operations() {
        let mut record = BlockTableRecord::new("TestBlock");
        record.add_object(ObjectId::new());
        record.add_object(ObjectId::new());
        assert_eq!(record.object_count(), 2);
        record.clear();
        assert_eq!(record.object_count(), 0);
    }

    #[test]
    fn test_block_table() {
        let table = BlockTable::new();
        assert!(table.has("*Model_Space"));
        assert_eq!(table.count(), 3);
    }

    #[test]
    fn test_block_table_operations() {
        let mut table = BlockTable::new();
        let record = BlockTableRecord::new("MyBlock");
        assert!(table.register(record));
        assert!(table.has("MyBlock"));
        assert!(table.remove("MyBlock"));
        assert!(!table.has("MyBlock"));
    }

    #[test]
    fn test_insert_entity() {
        let insert = InsertEntity::new("TestBlock")
            .with_transform(Point::new(100.0, 100.0, 0.0), (2.0, 2.0, 1.0), 45.0);
        assert_eq!(insert.block_name, "TestBlock");
        assert_eq!(insert.scale, (2.0, 2.0, 1.0));
        assert!((insert.rotation - 45.0 * std::f64::consts::PI / 180.0).abs() < 1e-10);
    }

    #[test]
    fn test_insert_entity_attributes() {
        let mut insert = InsertEntity::new("TestBlock");
        insert.add_attribute(Attribute::new("ATTR1", "Value1"));
        insert.add_attribute(Attribute::new("ATTR2", "Value2"));
        assert_eq!(insert.attribute_count(), 2);
        insert.set_attribute("ATTR1", "NewValue");
        assert_eq!(insert.get_attribute("ATTR1").unwrap().value, "NewValue");
    }

    #[test]
    fn test_insert_entity_array() {
        let insert = InsertEntity::new("TestBlock")
            .with_transform(Point::origin(), (1.0, 1.0, 1.0), 0.0);
        let array = insert.create_array(2, 2, 10.0, 20.0);
        assert_eq!(array.len(), 4);
    }

    #[test]
    fn test_drawing_unit_conversion() {
        assert!((DrawingUnit::Inches.to_millimeters(1.0) - 25.4).abs() < 1e-10);
        assert!((DrawingUnit::Millimeters.to_millimeters(25.4) - 25.4).abs() < 1e-10);
    }

    #[test]
    fn test_block_unit_from_drawing_unit() {
        assert_eq!(BlockUnit::from_drawing_unit(DrawingUnit::Millimeters), BlockUnit::Millimeters);
        assert_eq!(BlockUnit::from_drawing_unit(DrawingUnit::Inches), BlockUnit::Inches);
    }
}
