use serde::{Serialize, Deserialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::f64::consts::PI;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GeometricConstraint {
    Coincident,
    Perpendicular,
    Parallel,
    Tangent,
    Horizontal,
    Vertical,
    EqualLength,
    EqualRadius,
    Symmetric,
    Midpoint,
    Center,
    Fix,
}

impl Default for GeometricConstraint {
    fn default() -> Self {
        GeometricConstraint::Coincident
    }
}

impl GeometricConstraint {
    pub fn name(&self) -> &str {
        match self {
            GeometricConstraint::Coincident => "Coincident",
            GeometricConstraint::Perpendicular => "Perpendicular",
            GeometricConstraint::Parallel => "Parallel",
            GeometricConstraint::Tangent => "Tangent",
            GeometricConstraint::Horizontal => "Horizontal",
            GeometricConstraint::Vertical => "Vertical",
            GeometricConstraint::EqualLength => "Equal Length",
            GeometricConstraint::EqualRadius => "Equal Radius",
            GeometricConstraint::Symmetric => "Symmetric",
            GeometricConstraint::Midpoint => "Midpoint",
            GeometricConstraint::Center => "Center",
            GeometricConstraint::Fix => "Fix",
        }
    }

    pub fn icon(&self) -> &str {
        match self {
            GeometricConstraint::Coincident => "⭕",
            GeometricConstraint::Perpendicular => "⊥",
            GeometricConstraint::Parallel => "∥",
            GeometricConstraint::Tangent => "◎",
            GeometricConstraint::Horizontal => "—",
            GeometricConstraint::Vertical => "|",
            GeometricConstraint::EqualLength => "=",
            GeometricConstraint::EqualRadius => "≅",
            GeometricConstraint::Symmetric => "◈",
            GeometricConstraint::Midpoint => "◉",
            GeometricConstraint::Center => "⊕",
            GeometricConstraint::Fix => "📌",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConstraintEntity {
    pub id: String,
    pub constraint_type: GeometricConstraint,
    pub first_entity: String,
    pub second_entity: Option<String>,
    pub point_on_first: Option<crate::geometry::Point>,
    pub point_on_second: Option<crate::geometry::Point>,
    pub is_applied: bool,
    pub is_dragging: bool,
    pub reference: bool,
}

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

impl ConstraintEntity {
    pub fn new() -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            constraint_type: GeometricConstraint::Coincident,
            first_entity: String::new(),
            second_entity: None,
            point_on_first: None,
            point_on_second: None,
            is_applied: false,
            is_dragging: false,
            reference: false,
        }
    }

    pub fn with_type(mut self, constraint_type: GeometricConstraint) -> Self {
        self.constraint_type = constraint_type;
        self
    }

    pub fn with_first_entity(mut self, entity: &str) -> Self {
        self.first_entity = entity.to_string();
        self
    }

    pub fn with_second_entity(mut self, entity: &str) -> Self {
        self.second_entity = Some(entity.to_string());
        self
    }

    pub fn with_first_point(mut self, point: crate::geometry::Point) -> Self {
        self.point_on_first = Some(point);
        self
    }

    pub fn with_second_point(mut self, point: crate::geometry::Point) -> Self {
        self.point_on_second = Some(point);
        self
    }
}

impl fmt::Display for ConstraintEntity {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}: {} on {}",
            self.constraint_type.name(),
            self.first_entity,
            self.second_entity.as_ref().unwrap_or(&"None".to_string())
        )
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SolverState {
    pub solved: bool,
    pub iterations: u32,
    pub error: f64,
    pub max_error: f64,
    pub constrained_dof: u32,
    pub free_dof: u32,
}

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

impl SolverState {
    pub fn new() -> Self {
        Self {
            solved: true,
            iterations: 0,
            error: 0.0,
            max_error: 1e-6,
            constrained_dof: 0,
            free_dof: 0,
        }
    }
}

#[derive(Debug, Clone)]
pub struct GeometricSolver {
    pub constraints: Vec<ConstraintEntity>,
    pub entities: HashMap<String, EntityState>,
    pub tolerance: f64,
    pub max_iterations: u32,
    pub state: SolverState,
    pub auto_solve: bool,
    pub inference_constraints: bool,
}

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

impl GeometricSolver {
    pub fn new() -> Self {
        Self {
            constraints: Vec::new(),
            entities: HashMap::new(),
            tolerance: 1e-6,
            max_iterations: 1000,
            state: SolverState::new(),
            auto_solve: true,
            inference_constraints: true,
        }
    }

    pub fn add_constraint(&mut self, constraint: ConstraintEntity) -> bool {
        if self.validate_constraint(&constraint) {
            self.constraints.push(constraint.clone());
            if self.auto_solve {
                self.solve();
            }
            true
        } else {
            false
        }
    }

    pub fn remove_constraint(&mut self, constraint_id: &str) -> bool {
        let original_len = self.constraints.len();
        self.constraints.retain(|c| c.id != constraint_id);
        if self.auto_solve {
            self.solve();
        }
        self.constraints.len() != original_len
    }

    pub fn get_constraint(&self, constraint_id: &str) -> Option<&ConstraintEntity> {
        self.constraints.iter().find(|c| c.id == constraint_id)
    }

    pub fn constraints_for_entity(&self, entity_id: &str) -> Vec<&ConstraintEntity> {
        self.constraints
            .iter()
            .filter(|c| c.first_entity == entity_id || c.second_entity.as_ref() == Some(&entity_id.to_string()))
            .collect()
    }

    pub fn validate_constraint(&self, constraint: &ConstraintEntity) -> bool {
        match constraint.constraint_type {
            GeometricConstraint::Coincident
            | GeometricConstraint::Perpendicular
            | GeometricConstraint::Parallel
            | GeometricConstraint::Tangent
            | GeometricConstraint::EqualLength
            | GeometricConstraint::EqualRadius
            | GeometricConstraint::Symmetric => constraint.second_entity.is_some(),
            GeometricConstraint::Horizontal
            | GeometricConstraint::Vertical
            | GeometricConstraint::Midpoint
            | GeometricConstraint::Fix => constraint.second_entity.is_none(),
            GeometricConstraint::Center => true,
        }
    }

    pub fn solve(&mut self) -> &SolverState {
        self.state.iterations = 0;
        self.state.error = 0.0;
        self.state.solved = true;

        let mut converged = false;
        let mut error = f64::MAX;

        for _ in 0..self.max_iterations {
            self.state.iterations += 1;
            error = self.evaluate_constraints();

            if error < self.tolerance {
                converged = true;
                break;
            }

            self.apply_corrections(error);
        }

        self.state.error = error;
        self.state.solved = converged;

        self.calculate_dof();
        &self.state
    }

    fn evaluate_constraints(&self) -> f64 {
        let mut max_error = 0.0;

        for constraint in &self.constraints {
            let error = match constraint.constraint_type {
                GeometricConstraint::Coincident => self.evaluate_coincident(constraint),
                GeometricConstraint::Perpendicular => self.evaluate_perpendicular(constraint),
                GeometricConstraint::Parallel => self.evaluate_parallel(constraint),
                GeometricConstraint::Tangent => self.evaluate_tangent(constraint),
                GeometricConstraint::Horizontal => self.evaluate_horizontal(constraint),
                GeometricConstraint::Vertical => self.evaluate_vertical(constraint),
                GeometricConstraint::EqualLength => self.evaluate_equal_length(constraint),
                GeometricConstraint::EqualRadius => self.evaluate_equal_radius(constraint),
                GeometricConstraint::Symmetric => self.evaluate_symmetric(constraint),
                GeometricConstraint::Midpoint => self.evaluate_midpoint(constraint),
                GeometricConstraint::Center => self.evaluate_center(constraint),
                GeometricConstraint::Fix => self.evaluate_fix(constraint),
            };
            max_error = max_error.max(error);
        }

        max_error
    }

    fn evaluate_coincident(&self, constraint: &ConstraintEntity) -> f64 {
        if let (Some(first), Some(second)) = (
            self.get_entity_position(&constraint.first_entity),
            constraint.second_entity.as_ref().and_then(|e| self.get_entity_position(e)),
        ) {
            first.distance_to(&second)
        } else {
            0.0
        }
    }

    fn evaluate_perpendicular(&self, constraint: &ConstraintEntity) -> f64 {
        if let (Some(first), Some(second)) = (
            self.get_entity_direction(&constraint.first_entity),
            self.get_entity_direction(&constraint.second_entity.as_ref().unwrap()),
        ) {
            let dot = first.x * second.x + first.y * second.y;
            let angle = dot.abs().acos();
            let target = PI / 2.0;
            (angle - target).abs()
        } else {
            0.0
        }
    }

    fn evaluate_parallel(&self, constraint: &ConstraintEntity) -> f64 {
        if let (Some(first), Some(second)) = (
            self.get_entity_direction(&constraint.first_entity),
            self.get_entity_direction(&constraint.second_entity.as_ref().unwrap()),
        ) {
            let dot = first.x * second.x + first.y * second.y;
            let angle = dot.abs().acos();
            angle.min(PI - angle)
        } else {
            0.0
        }
    }

    fn evaluate_tangent(&self, constraint: &ConstraintEntity) -> f64 {
        if let (Some(first), Some(second)) = (
            self.get_entity_direction(&constraint.first_entity),
            self.get_entity_direction(&constraint.second_entity.as_ref().unwrap()),
        ) {
            let center1 = self.get_entity_center(&constraint.first_entity);
            let center2 = self.get_entity_center(&constraint.second_entity.as_ref().unwrap());

            if let (Some(c1), Some(c2)) = (center1, center2) {
                let point1 = self.get_entity_closest_point(&constraint.first_entity, &c2);
                let point2 = self.get_entity_closest_point(&constraint.second_entity.as_ref().unwrap(), &c1);

                point1.distance_to(&point2)
            } else {
                let dot = first.x * second.x + first.y * second.y;
                let angle = dot.abs().acos();
                angle.min(PI - angle) * 100.0
            }
        } else {
            0.0
        }
    }

    fn evaluate_horizontal(&self, constraint: &ConstraintEntity) -> f64 {
        if let Some(direction) = self.get_entity_direction(&constraint.first_entity) {
            direction.y.abs()
        } else {
            0.0
        }
    }

    fn evaluate_vertical(&self, constraint: &ConstraintEntity) -> f64 {
        if let Some(direction) = self.get_entity_direction(&constraint.first_entity) {
            direction.x.abs()
        } else {
            0.0
        }
    }

    fn evaluate_equal_length(&self, constraint: &ConstraintEntity) -> f64 {
        let len1 = self.get_entity_length(&constraint.first_entity);
        let len2 = self.get_entity_length(constraint.second_entity.as_ref().unwrap());
        (len1 - len2).abs()
    }

    fn evaluate_equal_radius(&self, constraint: &ConstraintEntity) -> f64 {
        let r1 = self.get_entity_radius(&constraint.first_entity);
        let r2 = self.get_entity_radius(constraint.second_entity.as_ref().unwrap());
        (r1 - r2).abs()
    }

    fn evaluate_symmetric(&self, constraint: &ConstraintEntity) -> f64 {
        if let (Some(line1), Some(line2)) = (
            self.get_entity_points(&constraint.first_entity),
            constraint.second_entity.as_ref().and_then(|e| self.get_entity_points(e)),
        ) {
            let mid1 = Point::new(
                (line1.0.x + line1.1.x) / 2.0,
                (line1.0.y + line1.1.y) / 2.0,
                0.0,
            );
            let mid2 = Point::new(
                (line2.0.x + line2.1.x) / 2.0,
                (line2.0.y + line2.1.y) / 2.0,
                0.0,
            );
            mid1.distance_to(&mid2)
        } else {
            0.0
        }
    }

    fn evaluate_midpoint(&self, constraint: &ConstraintEntity) -> f64 {
        if let (Some(points), Some(entity_point)) = (
            self.get_entity_points(&constraint.first_entity),
            constraint.point_on_second.as_ref().or(constraint.point_on_first.as_ref()),
        ) {
            let mid = Point::new(
                (points.0.x + points.1.x) / 2.0,
                (points.0.y + points.1.y) / 2.0,
                0.0,
            );
            mid.distance_to(entity_point)
        } else {
            0.0
        }
    }

    fn evaluate_center(&self, constraint: &ConstraintEntity) -> f64 {
        if let Some(center) = self.get_entity_center(&constraint.first_entity) {
            if let Some(point) = constraint.point_on_second.as_ref().or(constraint.point_on_first.as_ref()) {
                center.distance_to(point)
            } else {
                0.0
            }
        } else {
            0.0
        }
    }

    fn evaluate_fix(&self, constraint: &ConstraintEntity) -> f64 {
        if let Some(state) = self.entities.get(&constraint.first_entity) {
            state.drag_distance
        } else {
            0.0
        }
    }

    fn apply_corrections(&mut self, _error: f64) {
        let constraints: Vec<_> = self.constraints.clone();
        for constraint in constraints {
            match constraint.constraint_type {
                GeometricConstraint::Coincident => self.apply_coincident_correction(&constraint),
                GeometricConstraint::Horizontal => self.apply_horizontal_correction(&constraint),
                GeometricConstraint::Vertical => self.apply_vertical_correction(&constraint),
                _ => {}
            }
        }
    }

    fn apply_coincident_correction(&mut self, constraint: &ConstraintEntity) {
        if let (Some(first_pos), Some(second_pos)) = (
            self.get_entity_position(&constraint.first_entity),
            constraint.second_entity.as_ref().and_then(|e| self.get_entity_position(e)),
        ) {
            let correction = Point::new(
                (second_pos.x - first_pos.x) / 2.0,
                (second_pos.y - first_pos.y) / 2.0,
                0.0,
            );
            self.move_entity(&constraint.first_entity, &correction);
            self.move_entity(constraint.second_entity.as_ref().unwrap(), &Point::new(-correction.x, -correction.y, 0.0));
        }
    }

    fn apply_horizontal_correction(&mut self, constraint: &ConstraintEntity) {
        if let Some(state) = self.entities.get_mut(&constraint.first_entity) {
            state.angle = 0.0;
        }
    }

    fn apply_vertical_correction(&mut self, constraint: &ConstraintEntity) {
        if let Some(state) = self.entities.get_mut(&constraint.first_entity) {
            state.angle = PI / 2.0;
        }
    }

    fn get_entity_position(&self, entity_id: &str) -> Option<Point> {
        self.entities.get(entity_id).map(|s| s.position)
    }

    fn get_entity_direction(&self, entity_id: &str) -> Option<Point> {
        self.entities.get(entity_id).map(|s| {
            Point::new(s.angle.cos(), s.angle.sin(), 0.0)
        })
    }

    fn get_entity_center(&self, entity_id: &str) -> Option<Point> {
        self.entities.get(entity_id).map(|s| s.center)
    }

    fn get_entity_points(&self, entity_id: &str) -> Option<(Point, Point)> {
        self.entities.get(entity_id).map(|s| {
            let start = Point::new(
                s.position.x + (s.angle - s.length / 2.0).cos() * s.length,
                s.position.y + (s.angle - s.length / 2.0).sin() * s.length,
                0.0,
            );
            let end = Point::new(
                s.position.x + (s.angle + s.length / 2.0).cos() * s.length,
                s.position.y + (s.angle + s.length / 2.0).sin() * s.length,
                0.0,
            );
            (start, end)
        })
    }

    fn get_entity_length(&self, entity_id: &str) -> f64 {
        self.entities.get(entity_id).map(|s| s.length).unwrap_or(0.0)
    }

    fn get_entity_radius(&self, entity_id: &str) -> f64 {
        self.entities.get(entity_id).map(|s| s.radius).unwrap_or(0.0)
    }

    fn get_entity_closest_point(&self, entity_id: &str, point: &Point) -> Point {
        if let Some(state) = self.entities.get(entity_id) {
            let dx = point.x - state.position.x;
            let dy = point.y - state.position.y;
            let angle = dy.atan2(dx);
            Point::new(
                state.position.x + angle.cos() * state.length / 2.0,
                state.position.y + angle.sin() * state.length / 2.0,
                0.0,
            )
        } else {
            *point
        }
    }

    fn move_entity(&mut self, entity_id: &str, delta: &Point) {
        if let Some(state) = self.entities.get_mut(entity_id) {
            state.position.x += delta.x;
            state.position.y += delta.y;
            state.center.x += delta.x;
            state.center.y += delta.y;
        }
    }

    fn calculate_dof(&mut self) {
        let mut total_dof = 0;
        let mut constrained_dof = 0;

        for entity in self.entities.values() {
            total_dof += 3;
            constrained_dof += entity.constrained_dof;
        }

        self.state.constrained_dof = constrained_dof;
        self.state.free_dof = total_dof - constrained_dof;
    }

    pub fn add_entity(&mut self, entity_id: &str, position: Point, angle: f64, length: f64) {
        self.entities.insert(
            entity_id.to_string(),
            EntityState {
                position,
                center: position,
                angle,
                length,
                radius: 0.0,
                constrained_dof: 0,
                drag_distance: 0.0,
            },
        );
    }

    pub fn set_entity_position(&mut self, entity_id: &str, position: Point) {
        if let Some(state) = self.entities.get_mut(entity_id) {
            let dx = position.x - state.position.x;
            let dy = position.y - state.position.y;
            state.position = position;
            state.center.x += dx;
            state.center.y += dy;
        }
    }

    pub fn clear_constraints(&mut self) {
        self.constraints.clear();
        for state in self.entities.values_mut() {
            state.constrained_dof = 0;
        }
        if self.auto_solve {
            self.solve();
        }
    }

    pub fn constraint_count(&self) -> usize {
        self.constraints.len()
    }

    pub fn is_fully_constrained(&self) -> bool {
        self.state.free_dof == 0
    }

    pub fn is_over_constrained(&self) -> bool {
        self.state.constrained_dof > self.entities.len() * 3
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EntityState {
    pub position: Point,
    pub center: Point,
    pub angle: f64,
    pub length: f64,
    pub radius: f64,
    pub constrained_dof: u32,
    pub drag_distance: f64,
}

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

impl EntityState {
    pub fn new() -> Self {
        Self {
            position: Point::origin(),
            center: Point::origin(),
            angle: 0.0,
            length: 0.0,
            radius: 0.0,
            constrained_dof: 0,
            drag_distance: 0.0,
        }
    }
}

pub type Point = crate::geometry::Point;

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

    #[test]
    fn test_constraint_entity_creation() {
        let constraint = ConstraintEntity::new()
            .with_type(GeometricConstraint::Coincident)
            .with_first_entity("line1")
            .with_second_entity("line2");

        assert_eq!(constraint.constraint_type, GeometricConstraint::Coincident);
        assert_eq!(constraint.first_entity, "line1");
        assert_eq!(constraint.second_entity, Some("line2".to_string()));
    }

    #[test]
    fn test_solver_creation() {
        let solver = GeometricSolver::new();
        assert_eq!(solver.constraint_count(), 0);
        assert!(!solver.is_fully_constrained());
    }

    #[test]
    fn test_add_constraint() {
        let mut solver = GeometricSolver::new();
        solver.auto_solve = false;

        let constraint = ConstraintEntity::new()
            .with_type(GeometricConstraint::Coincident)
            .with_first_entity("line1")
            .with_second_entity("line2");

        assert!(solver.add_constraint(constraint));
        assert_eq!(solver.constraint_count(), 1);
    }

    #[test]
    fn test_validate_constraint() {
        let solver = GeometricSolver::new();

        let valid_constraint = ConstraintEntity::new()
            .with_type(GeometricConstraint::Coincident)
            .with_first_entity("line1")
            .with_second_entity("line2");

        let invalid_constraint = ConstraintEntity::new()
            .with_type(GeometricConstraint::Horizontal)
            .with_first_entity("line1")
            .with_second_entity("line2");

        assert!(solver.validate_constraint(&valid_constraint));
        assert!(!solver.validate_constraint(&invalid_constraint));
    }

    #[test]
    fn test_remove_constraint() {
        let mut solver = GeometricSolver::new();
        solver.auto_solve = false;

        let constraint = ConstraintEntity::new()
            .with_type(GeometricConstraint::Coincident)
            .with_first_entity("line1")
            .with_second_entity("line2");

        solver.add_constraint(constraint.clone());
        assert_eq!(solver.constraint_count(), 1);

        solver.remove_constraint(&constraint.id);
        assert_eq!(solver.constraint_count(), 0);
    }

    #[test]
    fn test_add_entity() {
        let mut solver = GeometricSolver::new();
        solver.add_entity("line1", Point::new(0.0, 0.0, 0.0), 0.0, 10.0);

        assert!(solver.entities.contains_key("line1"));
        let state = solver.entities.get("line1").unwrap();
        assert!((state.position.x - 0.0).abs() < 1e-10);
        assert!((state.length - 10.0).abs() < 1e-10);
    }

    #[test]
    fn test_constraint_types() {
        assert_eq!(GeometricConstraint::Coincident.name(), "Coincident");
        assert_eq!(GeometricConstraint::Perpendicular.name(), "Perpendicular");
        assert_eq!(GeometricConstraint::Parallel.name(), "Parallel");
        assert_eq!(GeometricConstraint::Tangent.name(), "Tangent");
        assert_eq!(GeometricConstraint::Horizontal.name(), "Horizontal");
        assert_eq!(GeometricConstraint::Vertical.name(), "Vertical");
    }

    #[test]
    fn test_constraint_icons() {
        assert_eq!(GeometricConstraint::Coincident.icon(), "⭕");
        assert_eq!(GeometricConstraint::Horizontal.icon(), "—");
        assert_eq!(GeometricConstraint::Vertical.icon(), "|");
        assert_eq!(GeometricConstraint::Fix.icon(), "📌");
    }

    #[test]
    fn test_solver_state() {
        let state = SolverState::new();
        assert!(state.solved);
        assert_eq!(state.iterations, 0);
        assert!((state.error - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_constraints_for_entity() {
        let mut solver = GeometricSolver::new();
        solver.auto_solve = false;

        solver.add_constraint(
            ConstraintEntity::new()
                .with_type(GeometricConstraint::Coincident)
                .with_first_entity("line1")
                .with_second_entity("line2"),
        );

        solver.add_constraint(
            ConstraintEntity::new()
                .with_type(GeometricConstraint::Horizontal)
                .with_first_entity("line1"),
        );

        let constraints = solver.constraints_for_entity("line1");
        assert_eq!(constraints.len(), 2);
    }

    #[test]
    fn test_clear_constraints() {
        let mut solver = GeometricSolver::new();
        solver.auto_solve = false;

        solver.add_constraint(ConstraintEntity::new()
            .with_type(GeometricConstraint::Coincident)
            .with_first_entity("line1")
            .with_second_entity("line2"));

        solver.add_constraint(ConstraintEntity::new()
            .with_type(GeometricConstraint::Horizontal)
            .with_first_entity("line1"));

        assert_eq!(solver.constraint_count(), 2);
        solver.clear_constraints();
        assert_eq!(solver.constraint_count(), 0);
    }

    #[test]
    fn test_dof_calculation() {
        let mut solver = GeometricSolver::new();
        solver.add_entity("line1", Point::new(0.0, 0.0, 0.0), 0.0, 10.0);
        solver.add_entity("line2", Point::new(10.0, 0.0, 0.0), PI / 2.0, 10.0);

        solver.auto_solve = false;
        solver.add_constraint(ConstraintEntity::new()
            .with_type(GeometricConstraint::Coincident)
            .with_first_entity("line1")
            .with_second_entity("line2"));

        solver.solve();

        assert!(solver.state.free_dof >= 0);
    }
}
