use std::fs;
use std::io::{self, Read};
use std::path::Path;
use xml::reader::{EventReader, XmlEvent};

/// Result type for XML validation operations
pub type XmlValidationResult = Result<(), XmlValidationError>;

/// Errors that can occur during XML validation
#[derive(Debug, thiserror::Error)]
pub enum XmlValidationError {
    #[error("IO error: {0}")]
    Io(#[from] io::Error),
    #[error("XML parsing error: {0}")]
    XmlParsing(#[from] xml::reader::Error),
    #[error("File not found: {0}")]
    FileNotFound(String),
    #[error("Schema file not found: {0}")]
    SchemaNotFound(String),
    #[error("XML validation failed: {0}")]
    ValidationFailed(String),
}

/// Validates an XML file against basic well-formedness rules
///
/// This function checks if the XML content is syntactically valid.
/// For full XSD schema validation, additional libraries would be needed.
///
/// # Arguments
/// * `xml_file_path` - Path to the XML file to validate
/// * `schema_file_path` - Path to the XSD schema file (currently used for existence check)
///
/// # Returns
/// * `Ok(())` if the XML is well-formed
/// * `Err(XmlValidationError)` if validation fails
pub fn validate_xml_file<P: AsRef<Path>>(
    xml_file_path: P,
    schema_file_path: P,
) -> XmlValidationResult {
    let xml_path = xml_file_path.as_ref();
    let schema_path = schema_file_path.as_ref();

    // Check if XML file exists
    if !xml_path.exists() {
        return Err(XmlValidationError::FileNotFound(
            xml_path.display().to_string(),
        ));
    }

    // Check if schema file exists
    if !schema_path.exists() {
        return Err(XmlValidationError::SchemaNotFound(
            schema_path.display().to_string(),
        ));
    }

    // Read XML content
    let xml_content = fs::read_to_string(xml_path)?;

    // For now, we perform basic well-formedness validation
    // In the future, this could be extended with proper XSD validation
    validate_xml_string(&xml_content, Some(schema_path))
}

/// Validates an XML string against basic well-formedness rules
///
/// This function checks if the XML content is syntactically valid.
/// For full XSD schema validation, additional libraries would be needed.
///
/// # Arguments
/// * `xml_content` - The XML content as a string
/// * `schema_file_path` - Optional path to the XSD schema file (currently used for existence check)
///
/// # Returns
/// * `Ok(())` if the XML is well-formed
/// * `Err(XmlValidationError)` if validation fails
pub fn validate_xml_string<P: AsRef<Path>>(
    xml_content: &str,
    schema_file_path: Option<P>,
) -> XmlValidationResult {
    // Check if schema file exists (if provided)
    if let Some(schema_path) = schema_file_path {
        let schema_path = schema_path.as_ref();
        if !schema_path.exists() {
            return Err(XmlValidationError::SchemaNotFound(
                schema_path.display().to_string(),
            ));
        }
    }

    // Parse the XML document for well-formedness
    let reader = EventReader::from_str(xml_content);
    let mut element_stack = Vec::new();
    let mut validation_errors = Vec::new();

    for event in reader {
        match event {
            Ok(XmlEvent::StartElement { name, .. }) => {
                element_stack.push(name.local_name.clone());
            }
            Ok(XmlEvent::EndElement { name }) => {
                if let Some(expected_name) = element_stack.pop() {
                    if name.local_name != expected_name {
                        validation_errors.push(format!(
                            "Mismatched element: expected closing tag for '{}', found '{}'",
                            expected_name, name.local_name
                        ));
                    }
                } else {
                    validation_errors
                        .push(format!("Unexpected closing tag: '{}'", name.local_name));
                }
            }
            Ok(XmlEvent::StartDocument { .. }) => {
                // Document start is expected
            }
            Ok(XmlEvent::EndDocument) => {
                // Document end is expected
            }
            Ok(_) => {
                // Other events (Characters, Comment, etc.) are fine
            }
            Err(e) => {
                validation_errors.push(format!("XML parsing error: {e}"));
            }
        }
    }

    // Check for unclosed elements
    if !element_stack.is_empty() {
        validation_errors.push(format!("Unclosed elements: {}", element_stack.join(", ")));
    }

    // Return result
    if validation_errors.is_empty() {
        Ok(())
    } else {
        Err(XmlValidationError::ValidationFailed(
            validation_errors.join("; "),
        ))
    }
}

/// Validates XML content from stdin
///
/// This function reads XML content from stdin and validates it.
///
/// # Arguments
/// * `schema_file_path` - Path to the XSD schema file
///
/// # Returns
/// * `Ok(())` if the XML is well-formed
/// * `Err(XmlValidationError)` if validation fails
#[allow(dead_code)]
pub fn validate_xml_from_stdin<P: AsRef<Path>>(schema_file_path: P) -> XmlValidationResult {
    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    validate_xml_string(&buffer, Some(schema_file_path))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;
    use tempfile::NamedTempFile;

    #[test]
    fn test_validate_well_formed_xml() {
        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
    <block type="p">
        <content>Hello world</content>
    </block>
</root>"#;

        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_malformed_xml() {
        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
    <block type="p">
        <content>Hello world</content>
    </root>"#; // Missing closing </block> tag

        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_err());

        if let Err(XmlValidationError::ValidationFailed(msg)) = result {
            assert!(msg.contains("Mismatched element") || msg.contains("Unexpected closing tag"));
        } else {
            panic!("Expected ValidationFailed error");
        }
    }

    #[test]
    fn test_validate_unclosed_elements() {
        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
    <block type="p">
        <content>Hello world</content>
    </block>"#; // Missing closing </root> tag

        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_err());

        if let Err(XmlValidationError::ValidationFailed(msg)) = result {
            assert!(msg.contains("Unclosed elements"));
        } else {
            panic!("Expected ValidationFailed error");
        }
    }

    #[test]
    fn test_validate_xml_file_not_found() {
        let result = validate_xml_file("nonexistent.xml", "schema.xsd");
        assert!(result.is_err());

        if let Err(XmlValidationError::FileNotFound(_)) = result {
            // Expected
        } else {
            panic!("Expected FileNotFound error");
        }
    }

    #[test]
    fn test_validate_schema_not_found() {
        let mut temp_file = NamedTempFile::new().unwrap();
        write!(temp_file, "<?xml version=\"1.0\"?><root></root>").unwrap();

        let result = validate_xml_file(temp_file.path(), Path::new("nonexistent.xsd"));
        assert!(result.is_err());

        if let Err(XmlValidationError::SchemaNotFound(_)) = result {
            // Expected
        } else {
            panic!("Expected SchemaNotFound error");
        }
    }

    #[test]
    fn test_validate_xml_file_success() {
        // Create temporary XML and schema files
        let mut xml_file = NamedTempFile::new().unwrap();
        let mut schema_file = NamedTempFile::new().unwrap();

        write!(
            xml_file,
            "<?xml version=\"1.0\"?><root><item>test</item></root>"
        )
        .unwrap();
        write!(schema_file, "<xs:schema></xs:schema>").unwrap(); // Dummy schema

        let result = validate_xml_file(xml_file.path(), schema_file.path());
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_xml_string_with_schema_file() {
        let mut schema_file = NamedTempFile::new().unwrap();
        write!(schema_file, "<xs:schema></xs:schema>").unwrap();

        let xml_content = "<?xml version=\"1.0\"?><root><item>test</item></root>";
        let result = validate_xml_string(xml_content, Some(schema_file.path()));
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_xml_string_schema_not_found() {
        let xml_content = "<?xml version=\"1.0\"?><root><item>test</item></root>";
        let result = validate_xml_string(xml_content, Some("nonexistent.xsd"));
        assert!(result.is_err());

        if let Err(XmlValidationError::SchemaNotFound(_)) = result {
            // Expected
        } else {
            panic!("Expected SchemaNotFound error");
        }
    }

    #[test]
    fn test_validate_xml_parsing_error() {
        let xml_content = "<?xml version=\"1.0\"?><root><item>unclosed content";
        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_xml_unexpected_closing_tag() {
        let xml_content = "<?xml version=\"1.0\"?><root></item></root>";
        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_err());

        if let Err(XmlValidationError::ValidationFailed(msg)) = result {
            assert!(msg.contains("Unexpected closing tag"));
        } else {
            panic!("Expected ValidationFailed error");
        }
    }

    #[test]
    fn test_validate_xml_with_various_events() {
        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
        <!-- This is a comment -->
        <root>
            <item attr="value">Content with text</item>
            <self-closing-tag />
            <![CDATA[Some CDATA content]]>
        </root>"#;

        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_xml_empty_document() {
        let xml_content = "<?xml version=\"1.0\"?><root></root>";
        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_xml_declaration_only() {
        // XML declaration without root element - this should fail
        let xml_content = "<?xml version=\"1.0\"?>";
        let result = validate_xml_string(xml_content, None::<&str>);
        // An XML document with only a declaration is typically invalid
        // as it needs at least one root element
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_xml_multiple_validation_errors() {
        let xml_content = r#"<?xml version="1.0"?>
        <root>
            <item>
            </wrong>
        </also-wrong>"#;

        let result = validate_xml_string(xml_content, None::<&str>);
        assert!(result.is_err());

        if let Err(XmlValidationError::ValidationFailed(msg)) = result {
            // Should contain multiple error types
            assert!(!msg.is_empty());
        } else {
            panic!("Expected ValidationFailed error");
        }
    }

    #[test]
    fn test_error_display() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
        let xml_err = XmlValidationError::Io(io_err);
        assert!(xml_err.to_string().contains("IO error"));

        let file_err = XmlValidationError::FileNotFound("test.xml".to_string());
        assert!(file_err.to_string().contains("File not found: test.xml"));

        let schema_err = XmlValidationError::SchemaNotFound("schema.xsd".to_string());
        assert!(schema_err
            .to_string()
            .contains("Schema file not found: schema.xsd"));

        let validation_err = XmlValidationError::ValidationFailed("validation failed".to_string());
        assert!(validation_err
            .to_string()
            .contains("XML validation failed: validation failed"));
    }

    #[test]
    fn test_xml_validation_result_type() {
        // Test that our type alias works correctly
        let result: XmlValidationResult = Ok(());
        assert!(result.is_ok());

        let error_result: XmlValidationResult =
            Err(XmlValidationError::FileNotFound("test".to_string()));
        assert!(error_result.is_err());
    }

    #[test]
    fn test_validate_xml_file_io_error() {
        // Try to create a file we can't read (this will test the IO error path)
        let mut xml_file = NamedTempFile::new().unwrap();
        let mut schema_file = NamedTempFile::new().unwrap();

        // Write binary content that might cause read issues
        xml_file.write_all(&[0xFF, 0xFE, 0xFF, 0xFE]).unwrap();
        write!(schema_file, "<schema/>").unwrap();

        // This should either succeed (if binary is handled) or fail gracefully
        let result = validate_xml_file(xml_file.path(), schema_file.path());
        // We don't assert success/failure as the behavior depends on the system
        // Just ensure it doesn't panic
        let _ = result;
    }

    #[test]
    fn test_validate_xml_from_stdin_with_mock_input() {
        // Create a temporary schema file for testing
        let mut schema_file = NamedTempFile::new().unwrap();
        write!(schema_file, "<xs:schema></xs:schema>").unwrap();

        // Test that the function exists and can be called
        // We can't easily mock stdin in unit tests, so we just verify the function signature
        // and that it would process the schema file path correctly

        // This test mainly ensures the function compiles and the schema path handling works
        let schema_path = schema_file.path();

        // Verify the schema file exists (this is what the function would check)
        assert!(schema_path.exists());

        // The function would read from stdin and then call validate_xml_string
        // We can't test the full flow without mocking stdin, but we can test
        // that the underlying components work
        let test_xml = "<?xml version=\"1.0\"?><root><item>test</item></root>";
        let result = validate_xml_string(test_xml, Some(schema_path));
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_xml_string_comprehensive_coverage() {
        // Additional edge case tests to improve coverage

        // Test with whitespace-only XML
        let whitespace_xml = "   \n\t  ";
        let result = validate_xml_string(whitespace_xml, None::<&str>);
        assert!(result.is_err());

        // Test with XML that has processing instructions
        let pi_xml = r#"<?xml version="1.0"?>
        <?xml-stylesheet type="text/xsl" href="style.xsl"?>
        <root>Content</root>"#;
        let result = validate_xml_string(pi_xml, None::<&str>);
        assert!(result.is_ok());

        // Test with XML that has DOCTYPE declaration
        let doctype_xml = r#"<?xml version="1.0"?>
        <!DOCTYPE root SYSTEM "example.dtd">
        <root>Content</root>"#;
        let result = validate_xml_string(doctype_xml, None::<&str>);
        assert!(result.is_ok());
    }

    #[test]
    fn test_xml_validation_error_variants() {
        // Test that all error variants can be created and displayed
        use std::io;

        // Test IO error variant
        let io_err = io::Error::new(io::ErrorKind::NotFound, "test error");
        let xml_io_error = XmlValidationError::Io(io_err);
        let error_string = format!("{xml_io_error}");
        assert!(error_string.contains("IO error"));

        // Test XmlParsing error variant - we can't easily create xml::reader::Error
        // so we test through actual parsing
        let invalid_xml = "<?xml version=\"1.0\"?><unclosed>";
        let result = validate_xml_string(invalid_xml, None::<&str>);
        assert!(result.is_err());

        // Test ValidationFailed variant
        let validation_error = XmlValidationError::ValidationFailed("test validation".to_string());
        let error_string = format!("{validation_error}");
        assert!(error_string.contains("XML validation failed"));
    }
}
