use crate::db::models::{CodeElement, Relationship};
use regex::Regex;

pub struct AndroidResourcesExtractor<'a> {
    source: &'a [u8],
    file_path: &'a str,
}

impl<'a> AndroidResourcesExtractor<'a> {
    pub fn new(source: &'a [u8], file_path: &'a str) -> Self {
        Self { source, file_path }
    }

    pub fn extract(&self) -> (Vec<CodeElement>, Vec<Relationship>) {
        let content = std::str::from_utf8(self.source).unwrap_or("");
        let mut elements = Vec::new();
        let mut relationships = Vec::new();

        let file_name = std::path::Path::new(self.file_path)
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("");

        let resource_type = self.detect_resource_type(file_name);

        elements.push(CodeElement {
            qualified_name: self.file_path.to_string(),
            element_type: resource_type.to_string(),
            name: file_name.to_string(),
            file_path: self.file_path.to_string(),
            language: "android".to_string(),
            ..Default::default()
        });

        self.extract_string_resources(content, &mut elements, &mut relationships);
        self.extract_color_resources(content, &mut elements, &mut relationships);
        self.extract_dimen_resources(content, &mut elements, &mut relationships);
        self.extract_style_resources(content, &mut elements, &mut relationships);
        self.extract_theme_resources(content, &mut elements, &mut relationships);
        self.extract_bool_resources(content, &mut elements, &mut relationships);
        self.extract_integer_resources(content, &mut elements, &mut relationships);
        self.extract_array_resources(content, &mut elements, &mut relationships);

        (elements, relationships)
    }

    fn detect_resource_type(&self, file_name: &str) -> &str {
        if file_name.starts_with("strings") {
            "android_strings"
        } else if file_name.starts_with("colors") {
            "android_colors"
        } else if file_name.starts_with("dimens") {
            "android_dimens"
        } else if file_name.starts_with("styles") {
            "android_styles"
        } else if file_name.starts_with("bools") || file_name.starts_with("bool") {
            "android_bools"
        } else if file_name.starts_with("integers") || file_name.starts_with("integer") {
            "android_integers"
        } else if file_name.starts_with("arrays") || file_name.starts_with("plurals") {
            "android_arrays"
        } else {
            "android_values"
        }
    }

    fn extract_string_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<string\s+name\s*=\s*"([^"]+)"[^>]*>([^<]*)</string>"#).unwrap();
        for cap in re.captures_iter(content) {
            if let (Some(name), Some(value)) = (cap.get(1), cap.get(2)) {
                let string_name = name.as_str().to_string();
                let string_value = value.as_str().trim().to_string();
                let qualified_name = format!("{}/@string/{}", self.file_path, string_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_string".to_string(),
                    name: string_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "value": string_value,
                        "resource_type": "string",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_string".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({
                        "value": string_value,
                    }),
                    ..Default::default()
                });
            }
        }
    }

    fn extract_color_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<color\s+name\s*=\s*"([^"]+)"[^>]*>([^<]*)</color>"#).unwrap();
        for cap in re.captures_iter(content) {
            if let (Some(name), Some(value)) = (cap.get(1), cap.get(2)) {
                let color_name = name.as_str().to_string();
                let color_value = value.as_str().trim().to_string();
                let qualified_name = format!("{}/@color/{}", self.file_path, color_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_color".to_string(),
                    name: color_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "value": color_value,
                        "resource_type": "color",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_color".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({
                        "value": color_value,
                    }),
                    ..Default::default()
                });
            }
        }
    }

    fn extract_dimen_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<dimen\s+name\s*=\s*"([^"]+)"[^>]*>([^<]*)</dimen>"#).unwrap();
        for cap in re.captures_iter(content) {
            if let (Some(name), Some(value)) = (cap.get(1), cap.get(2)) {
                let dimen_name = name.as_str().to_string();
                let dimen_value = value.as_str().trim().to_string();
                let qualified_name = format!("{}/@dimen/{}", self.file_path, dimen_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_dimen".to_string(),
                    name: dimen_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "value": dimen_value,
                        "resource_type": "dimen",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_dimen".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({
                        "value": dimen_value,
                    }),
                    ..Default::default()
                });
            }
        }
    }

    fn extract_style_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(
            r#"<style\s+name\s*=\s*"([^"]+)"(?:\s+parent\s*=\s*"([^"]*)")?[^>]*>[\s\S]*?</style>"#,
        )
        .unwrap();
        for cap in re.captures_iter(content) {
            if let Some(name) = cap.get(1) {
                let style_name = name.as_str().to_string();
                let parent = cap.get(2).map(|m| m.as_str().to_string());
                let qualified_name = format!("{}/@style/{}", self.file_path, style_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_style".to_string(),
                    name: style_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "parent": parent,
                        "resource_type": "style",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name.clone(),
                    rel_type: "defines_style".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({}),
                    ..Default::default()
                });

                if let Some(parent_name) = parent {
                    if !parent_name.is_empty() {
                        relationships.push(Relationship {
                            id: None,
                            source_qualified: qualified_name.clone(),
                            target_qualified: format!("@style/{}", parent_name),
                            rel_type: "inherits_from".to_string(),
                            confidence: 1.0,
                            metadata: serde_json::json!({
                                "parent": parent_name,
                            }),
                            ..Default::default()
                        });
                    }
                }
            }
        }
    }

    fn extract_theme_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<theme\s+name\s*=\s*"([^"]+)"[^>]*>[\s\S]*?</theme>"#).unwrap();
        for cap in re.captures_iter(content) {
            if let Some(name) = cap.get(1) {
                let theme_name = name.as_str().to_string();
                let qualified_name = format!("{}/@theme/{}", self.file_path, theme_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_theme".to_string(),
                    name: theme_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "resource_type": "theme",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_theme".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({}),
                    ..Default::default()
                });
            }
        }
    }

    fn extract_bool_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<bool\s+name\s*=\s*"([^"]+)"[^>]*>([^<]*)</bool>"#).unwrap();
        for cap in re.captures_iter(content) {
            if let (Some(name), Some(value)) = (cap.get(1), cap.get(2)) {
                let bool_name = name.as_str().to_string();
                let bool_value = value.as_str().trim().to_string();
                let qualified_name = format!("{}/@bool/{}", self.file_path, bool_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_bool".to_string(),
                    name: bool_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "value": bool_value,
                        "resource_type": "bool",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_bool".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({
                        "value": bool_value,
                    }),
                    ..Default::default()
                });
            }
        }
    }

    fn extract_integer_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<integer\s+name\s*=\s*"([^"]+)"[^>]*>([^<]*)</integer>"#).unwrap();
        for cap in re.captures_iter(content) {
            if let (Some(name), Some(value)) = (cap.get(1), cap.get(2)) {
                let int_name = name.as_str().to_string();
                let int_value = value.as_str().trim().to_string();
                let qualified_name = format!("{}/@integer/{}", self.file_path, int_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_integer".to_string(),
                    name: int_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "value": int_value,
                        "resource_type": "integer",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_integer".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({
                        "value": int_value,
                    }),
                    ..Default::default()
                });
            }
        }
    }

    fn extract_array_resources(
        &self,
        content: &str,
        elements: &mut Vec<CodeElement>,
        relationships: &mut Vec<Relationship>,
    ) {
        let re = Regex::new(r#"<(?:string-array|integer-array|plurals)\s+name\s*=\s*"([^"]+)"[^>]*>[\s\S]*?</(?:string-array|integer-array|plurals)>"#)
            .unwrap();
        for cap in re.captures_iter(content) {
            if let Some(name) = cap.get(1) {
                let array_name = name.as_str().to_string();
                let qualified_name = format!("{}/@array/{}", self.file_path, array_name);

                elements.push(CodeElement {
                    qualified_name: qualified_name.clone(),
                    element_type: "android_array".to_string(),
                    name: array_name.clone(),
                    file_path: self.file_path.to_string(),
                    language: "android".to_string(),
                    metadata: serde_json::json!({
                        "resource_type": "array",
                    }),
                    ..Default::default()
                });

                relationships.push(Relationship {
                    id: None,
                    source_qualified: self.file_path.to_string(),
                    target_qualified: qualified_name,
                    rel_type: "defines_array".to_string(),
                    confidence: 1.0,
                    metadata: serde_json::json!({}),
                    ..Default::default()
                });
            }
        }
    }
}

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

    #[test]
    fn test_extract_string_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">My App</string>
    <string name="greeting">Hello World!</string>
    <string name="empty_string"></string>
</resources>"#;
        let extractor = AndroidResourcesExtractor::new(source.as_slice(), "res/values/strings.xml");
        let (elements, relationships) = extractor.extract();

        let strings: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_string")
            .collect();
        assert_eq!(strings.len(), 3, "Should extract 3 string resources");
        assert_eq!(strings[0].name, "app_name");
        assert_eq!(strings[0].metadata["value"], "My App");

        let defs: Vec<_> = relationships
            .iter()
            .filter(|r| r.rel_type == "defines_string")
            .collect();
        assert_eq!(defs.len(), 3);
    }

    #[test]
    fn test_extract_color_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="primary">#FF6200EE</color>
    <color name="secondary">#FF03DAC5</color>
</resources>"#;
        let extractor = AndroidResourcesExtractor::new(source.as_slice(), "res/values/colors.xml");
        let (elements, relationships) = extractor.extract();

        let colors: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_color")
            .collect();
        assert_eq!(colors.len(), 2, "Should extract 2 color resources");
        assert_eq!(colors[0].name, "primary");
        assert_eq!(colors[0].metadata["value"], "#FF6200EE");
    }

    #[test]
    fn test_extract_style_with_parent() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
        <item name="colorPrimary">@color/primary</item>
    </style>
    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
    </style>
</resources>"#;
        let extractor = AndroidResourcesExtractor::new(source.as_slice(), "res/values/styles.xml");
        let (elements, relationships) = extractor.extract();

        let styles: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_style")
            .collect();
        assert_eq!(styles.len(), 2, "Should extract 2 style resources");
        assert_eq!(styles[0].name, "AppTheme");
        assert_eq!(
            styles[0].metadata["parent"],
            "Theme.MaterialComponents.Light.DarkActionBar"
        );

        let inherits: Vec<_> = relationships
            .iter()
            .filter(|r| r.rel_type == "inherits_from")
            .collect();
        assert_eq!(
            inherits.len(),
            1,
            "Should extract 1 inheritance relationship"
        );
    }

    #[test]
    fn test_extract_dimen_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="padding_small">8dp</dimen>
    <dimen name="padding_medium">16dp</dimen>
    <dimen name="text_size_large">24sp</dimen>
</resources>"#;
        let extractor = AndroidResourcesExtractor::new(source.as_slice(), "res/values/dimens.xml");
        let (elements, _) = extractor.extract();

        let dimens: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_dimen")
            .collect();
        assert_eq!(dimens.len(), 3, "Should extract 3 dimen resources");
        assert_eq!(dimens[0].name, "padding_small");
        assert_eq!(dimens[0].metadata["value"], "8dp");
    }

    #[test]
    fn test_extract_bool_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="is_tablet">false</bool>
    <bool name="enable_logging">true</bool>
</resources>"#;
        let extractor = AndroidResourcesExtractor::new(source.as_slice(), "res/values/bools.xml");
        let (elements, _) = extractor.extract();

        let bools: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_bool")
            .collect();
        assert_eq!(bools.len(), 2, "Should extract 2 bool resources");
    }

    #[test]
    fn test_extract_integer_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="max_retries">3</integer>
    <integer name="timeout_seconds">30</integer>
</resources>"#;
        let extractor =
            AndroidResourcesExtractor::new(source.as_slice(), "res/values/integers.xml");
        let (elements, _) = extractor.extract();

        let ints: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_integer")
            .collect();
        assert_eq!(ints.len(), 2, "Should extract 2 integer resources");
    }

    #[test]
    fn test_extract_array_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="planets">
        <item>Mercury</item>
        <item>Venus</item>
    </string-array>
    <integer-array name="scores">
        <item>100</item>
        <item>200</item>
    </integer-array>
</resources>"#;
        let extractor = AndroidResourcesExtractor::new(source.as_slice(), "res/values/arrays.xml");
        let (elements, _) = extractor.extract();

        let arrays: Vec<_> = elements
            .iter()
            .filter(|e| e.element_type == "android_array")
            .collect();
        assert_eq!(arrays.len(), 2, "Should extract 2 array resources");
    }

    #[test]
    fn test_mixed_resources() {
        let source = br#"<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">My App</string>
    <color name="primary">#FF6200EE</color>
    <dimen name="padding">16dp</dimen>
    <bool name="is_tablet">false</bool>
    <integer name="max_retries">3</integer>
    <style name="AppTheme" parent="Theme.MaterialComponents">
        <item name="colorPrimary">@color/primary</item>
    </style>
</resources>"#;
        let extractor =
            AndroidResourcesExtractor::new(source.as_slice(), "res/values/resources.xml");
        let (elements, relationships) = extractor.extract();

        assert!(elements.len() >= 6, "Should extract at least 6 resources");
        assert!(
            relationships.len() >= 6,
            "Should extract at least 6 relationships"
        );
    }
}
