use crate::geometry::Point;
use crate::math::Vector2;
use std::fmt;
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Line {
    pub start: Point,
    pub end: Point,
}

impl Line {
    #[inline]
    pub fn new(start: Point, end: Point) -> Self {
        Self { start, end }
    }

    #[inline]
    pub fn new2d(start: Point, end: Point) -> Self {
        Self { start, end }
    }

    #[inline]
    pub fn direction(&self) -> Vector2 {
        (self.end.to_vector2() - self.start.to_vector2()).normalize()
    }

    #[inline]
    pub fn length(&self) -> f64 {
        self.start.distance_to(&self.end)
    }

    #[inline]
    pub fn midpoint(&self) -> Point {
        Point::new(
            (self.start.x + self.end.x) / 2.0,
            (self.start.y + self.end.y) / 2.0,
            (self.start.z + self.end.z) / 2.0,
        )
    }

    #[inline]
    pub fn point_at_parameter(&self, t: f64) -> Point {
        Point::new(
            self.start.x + (self.end.x - self.start.x) * t,
            self.start.y + (self.end.y - self.start.y) * t,
            self.start.z + (self.end.z - self.start.z) * t,
        )
    }

    #[inline]
    pub fn is_horizontal(&self) -> bool {
        (self.end.y - self.start.y).abs() < 1e-10
    }

    #[inline]
    pub fn is_vertical(&self) -> bool {
        (self.end.x - self.start.x).abs() < 1e-10
    }

    #[inline]
    pub fn closest_point(&self, p: &Point) -> Point {
        let dir = self.direction();
        let to_point = p.to_vector2() - self.start.to_vector2();
        let t = to_point.dot(&dir).clamp(0.0, 1.0);
        self.point_at_parameter(t)
    }

    #[inline]
    pub fn distance_to_point(&self, p: &Point) -> f64 {
        p.distance_to(&self.closest_point(p))
    }
}

impl fmt::Display for Line {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Line({} -> {})", self.start, self.end)
    }
}

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

    #[test]
    fn test_line_creation() {
        let start = Point::new(0.0, 0.0, 0.0);
        let end = Point::new(1.0, 1.0, 0.0);
        let line = Line::new(start, end);
        
        assert_eq!(line.start, start);
        assert_eq!(line.end, end);
    }

    #[test]
    fn test_line_length() {
        let start = Point::new(0.0, 0.0, 0.0);
        let end = Point::new(3.0, 4.0, 0.0);
        let line = Line::new(start, end);
        
        assert!((line.length() - 5.0).abs() < 1e-10);
    }

    #[test]
    fn test_line_midpoint() {
        let start = Point::new(0.0, 0.0, 0.0);
        let end = Point::new(2.0, 2.0, 0.0);
        let line = Line::new(start, end);
        let midpoint = line.midpoint();
        
        assert!((midpoint.x - 1.0).abs() < 1e-10);
        assert!((midpoint.y - 1.0).abs() < 1e-10);
    }

    #[test]
    fn test_line_direction() {
        let start = Point::new(0.0, 0.0, 0.0);
        let end = Point::new(3.0, 4.0, 0.0);
        let line = Line::new(start, end);
        let direction = line.direction();
        
        assert!((direction.magnitude() - 1.0).abs() < 1e-10);
        assert!((direction.x - 0.6).abs() < 1e-10);
        assert!((direction.y - 0.8).abs() < 1e-10);
    }

    #[test]
    fn test_line_point_at_parameter() {
        let start = Point::new(0.0, 0.0, 0.0);
        let end = Point::new(10.0, 10.0, 0.0);
        let line = Line::new(start, end);
        
        let t0 = line.point_at_parameter(0.0);
        assert_eq!(t0, start);
        
        let t1 = line.point_at_parameter(1.0);
        assert_eq!(t1, end);
        
        let t05 = line.point_at_parameter(0.5);
        assert!((t05.x - 5.0).abs() < 1e-10);
        assert!((t05.y - 5.0).abs() < 1e-10);
    }

    #[test]
    fn test_line_is_horizontal() {
        let horizontal = Line::new(Point::new(0.0, 0.0, 0.0), Point::new(10.0, 0.0, 0.0));
        let not_horizontal = Line::new(Point::new(0.0, 0.0, 0.0), Point::new(10.0, 1.0, 0.0));
        
        assert!(horizontal.is_horizontal());
        assert!(!not_horizontal.is_horizontal());
    }

    #[test]
    fn test_line_is_vertical() {
        let vertical = Line::new(Point::new(0.0, 0.0, 0.0), Point::new(0.0, 10.0, 0.0));
        let not_vertical = Line::new(Point::new(0.0, 0.0, 0.0), Point::new(1.0, 10.0, 0.0));
        
        assert!(vertical.is_vertical());
        assert!(!not_vertical.is_vertical());
    }

    #[test]
    fn test_line_closest_point() {
        let line = Line::new(Point::new(0.0, 0.0, 0.0), Point::new(10.0, 0.0, 0.0));
        
        let point_on_line = line.closest_point(&Point::new(5.0, 0.0, 0.0));
        assert!((point_on_line.x - 5.0).abs() < 1e-10);
        assert!((point_on_line.y - 0.0).abs() < 1e-10);
        
        let point_below = line.closest_point(&Point::new(5.0, -5.0, 0.0));
        assert!((point_below.x - 5.0).abs() < 1e-10);
        assert!((point_below.y - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_line_distance_to_point() {
        let line = Line::new(Point::new(0.0, 0.0, 0.0), Point::new(10.0, 0.0, 0.0));
        
        let distance = line.distance_to_point(&Point::new(5.0, 5.0, 0.0));
        assert!((distance - 5.0).abs() < 1e-10);
        
        let distance_on_line = line.distance_to_point(&Point::new(5.0, 0.0, 0.0));
        assert!((distance_on_line - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_line_display() {
        let line = Line::new(Point::new(1.0, 2.0, 0.0), Point::new(3.0, 4.0, 0.0));
        let display = format!("{}", line);
        assert!(display.contains("Line"));
        assert!(display.contains("1"));
        assert!(display.contains("2"));
        assert!(display.contains("3"));
        assert!(display.contains("4"));
    }

    #[test]
    fn test_line_clone() {
        let line1 = Line::new(Point::new(1.0, 2.0, 0.0), Point::new(3.0, 4.0, 0.0));
        let line2 = line1;
        assert_eq!(line1.start, line2.start);
        assert_eq!(line1.end, line2.end);
    }

    #[test]
    fn test_line_zero_length() {
        let start = Point::new(5.0, 5.0, 0.0);
        let end = Point::new(5.0, 5.0, 0.0);
        let line = Line::new(start, end);
        
        assert!((line.length() - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_validation_positive() {
        assert!(validation::positive(1.0, "test").is_ok());
        assert!(validation::positive(0.0, "test").is_err());
        assert!(validation::positive(-1.0, "test").is_err());
    }

    #[test]
    fn test_validation_in_range() {
        assert!(validation::in_range(5.0, 0.0, 10.0, "test").is_ok());
        assert!(validation::in_range(-1.0, 0.0, 10.0, "test").is_err());
    }

    #[test]
    fn test_validation_non_negative() {
        assert!(validation::non_negative(0.0, "test").is_ok());
        assert!(validation::non_negative(1.0, "test").is_ok());
        assert!(validation::non_negative(-1.0, "test").is_err());
    }

    #[test]
    fn test_validation_scale_factor() {
        assert!(validation::scale_factor(1.0, "test").is_ok());
        assert!(validation::scale_factor(0.0, "test").is_err());
        assert!(validation::scale_factor(-1.0, "test").is_err());
        assert!(validation::scale_factor(f64::INFINITY, "test").is_err());
    }

    #[test]
    fn test_validation_coordinate() {
        assert!(validation::coordinate(1.0, "test").is_ok());
        assert!(validation::coordinate(f64::NAN, "test").is_err());
        assert!(validation::coordinate(f64::INFINITY, "test").is_err());
    }
}
