use std::ffi::OsString;

use once_cell::sync::Lazy;
use regex::Regex;

use crate::{
    handlers::build_upload_routes,
    route_builder::{PrintRoute, Route, RouteGenerator, route_params::RouteParams},
};

static RE_DIR_UPLOAD: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^(\$)?\{upload\}(\{temp\})?(-(.+))?$").unwrap());

const ELEMENT_IS_PROTECTED: usize = 1;
const ELEMENT_IS_TEMPORARY: usize = 2;
const ELEMENT_ROUTE: usize = 4;

/// Path parameter used by generated download routes.
pub const FILE_NAME_PARAM: &str = "{file_name}";

/// Upload route set generated from a `{upload}` directory.
#[derive(Debug, Clone, PartialEq)]
pub struct RouteUpload {
    /// Upload directory path.
    pub path: OsString,
    /// Base route for upload operations.
    pub route: String,
    /// Whether uploaded files are removed when the server stops.
    pub is_temporary: bool,
    /// Whether this route requires auth middleware.
    pub is_protected: bool,
    /// Optional response delay in milliseconds.
    pub delay: Option<u16>,
    /// Optional upload endpoint suffix.
    pub upload_endpoint: Option<String>,
    /// Optional download endpoint suffix.
    pub download_endpoint: Option<String>,
    /// Optional list-files endpoint suffix.
    pub list_files_endpoint: Option<String>,
}

impl RouteUpload {
    /// Parses route parameters as an upload directory route definition.
    pub fn try_parse(route_params: RouteParams) -> Route {
        if let Some(captures) = RE_DIR_UPLOAD.captures(&route_params.file_name) {
            let config = route_params.config.clone();
            let route_config = config.route.clone().unwrap_or_default();
            let upload_config = config.upload.clone().unwrap_or_default();

            // From config
            let delay = route_config.delay;
            let is_protected = route_config.protect.unwrap_or(false);
            let is_temporary = upload_config.temporary.unwrap_or(false);
            let upload_endpoint = upload_config.upload_endpoint;
            let download_endpoint = upload_config.download_endpoint;
            let list_files_endpoint = upload_config.list_files_endpoint;

            // From file
            let is_protected = is_protected || captures.get(ELEMENT_IS_PROTECTED).is_some();
            let is_temporary = is_temporary || captures.get(ELEMENT_IS_TEMPORARY).is_some();
            let uploads_route = if let Some(route) = captures.get(ELEMENT_ROUTE) {
                route.as_str()
            } else {
                "upload"
            };

            let route = route_config
                .remap
                .unwrap_or(format!("{}/{}", route_params.parent_route, uploads_route));

            let route_upload = Self {
                path: route_params.file_path,
                route: route.to_string(),
                delay,
                is_temporary,
                is_protected,
                upload_endpoint,
                download_endpoint,
                list_files_endpoint,
            };

            return Route::Upload(route_upload);
        }

        Route::None
    }

    /// Builds a full route by appending an optional endpoint suffix to the base route.
    pub fn get_route(&self, endpoint: &Option<String>) -> String {
        match endpoint {
            Some(endpoint) => format!("{}{}", self.route, endpoint),
            None => self.route.clone(),
        }
    }

    /// Returns the generated upload route.
    pub fn get_upload_route(&self) -> String {
        self.get_route(&self.upload_endpoint)
    }

    /// Returns the generated download route with a `{file_name}` path parameter.
    pub fn get_download_route(&self) -> String {
        format!(
            "{}/{}",
            self.get_route(&self.download_endpoint),
            FILE_NAME_PARAM
        )
    }

    /// Returns the generated list-files route.
    pub fn get_list_files_route(&self) -> String {
        self.get_route(&self.list_files_endpoint)
    }
}

impl RouteGenerator for RouteUpload {
    fn make_routes(&self, app: &mut crate::app::App) {
        let path = self.path.to_string_lossy();
        app.push_uploads_config(path.to_string(), self.is_temporary);

        build_upload_routes(app, self);
    }
}

impl PrintRoute for RouteUpload {
    fn println(&self) {
        println!(
            "✔️ Mapped uploads routes from folder {} to {}",
            self.path.to_string_lossy(),
            self.route
        );
        println!(
            "   ├── upload route to     POST {}",
            self.get_upload_route()
        );
        println!(
            "   ├── download route to   GET {}",
            self.get_download_route()
        );
        println!(
            "   └── list files route to GET {}",
            self.get_list_files_route()
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::route_builder::config::{Config, ConfigStore};
    use crate::route_builder::route_params::RouteParams;
    use std::fs;
    use std::path::Path;
    use tempfile::TempDir;

    fn create_test_dir(dir: &Path, dirname: &str) -> std::fs::DirEntry {
        let dir_path = dir.join(dirname);
        fs::create_dir(&dir_path).unwrap();
        let mut entries = dir.read_dir().unwrap();
        entries
            .find(|entry| entry.as_ref().unwrap().file_name() == dirname)
            .unwrap()
            .unwrap()
    }

    #[test]
    fn test_try_parse_basic_upload_directory() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/upload");
                assert!(!route_upload.is_temporary);
                assert!(!route_upload.is_protected);
                let expected_path = temp_dir.path().join("{upload}").into_os_string();
                assert_eq!(route_upload.path, expected_path);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_make_routes_and_print_delegate_to_upload_builder() {
        let temp_dir = TempDir::new().unwrap();
        let uploads = temp_dir.path().join("{upload}");
        std::fs::create_dir(&uploads).unwrap();
        let route_upload = RouteUpload {
            path: uploads.into_os_string(),
            route: "/files".to_string(),
            is_temporary: true,
            is_protected: false,
            delay: None,
            upload_endpoint: Some("/upload".to_string()),
            download_endpoint: Some("/download".to_string()),
            list_files_endpoint: Some("/list".to_string()),
        };
        let mut app = crate::app::App::default();
        route_upload.make_routes(&mut app);
        route_upload.println();
        assert!(
            app.pages
                .lock()
                .unwrap()
                .render_index()
                .contains("/files/upload")
        );
    }

    #[test]
    fn test_try_parse_protected_upload_directory() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "${upload}");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/upload");
                assert!(!route_upload.is_temporary);
                assert!(route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_temporary_upload_directory() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}{temp}");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/upload");
                assert!(route_upload.is_temporary);
                assert!(!route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_protected_temporary_upload() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "${upload}{temp}");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/upload");
                assert!(route_upload.is_temporary);
                assert!(route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_upload_with_custom_route() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}-files");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/files");
                assert!(!route_upload.is_temporary);
                assert!(!route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_temporary_upload_with_custom_route() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}{temp}-media");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/media");
                assert!(route_upload.is_temporary);
                assert!(!route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_protected_upload_with_custom_route() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "${upload}-documents");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/documents");
                assert!(!route_upload.is_temporary);
                assert!(route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_full_featured_upload() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "${upload}{temp}-assets");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/assets");
                assert!(route_upload.is_temporary);
                assert!(route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_inherited_protection() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}");
        let route_params = RouteParams::new(
            "/api/admin",
            &entry,
            Config::default().with_protect(true),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/admin/upload");
                assert!(!route_upload.is_temporary);
                assert!(route_upload.is_protected); // Inherited from parent
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_nested_route_paths() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}-images");
        let route_params = RouteParams::new(
            "/api/v1/content",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/api/v1/content/images");
                assert!(!route_upload.is_temporary);
                assert!(!route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_upload_with_empty_parent_route() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}-data");
        let route_params = RouteParams::new(
            "",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                assert_eq!(route_upload.route, "/data");
                assert!(!route_upload.is_temporary);
                assert!(!route_upload.is_protected);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_complex_custom_route_names() {
        let temp_dir = TempDir::new().unwrap();
        let test_cases = vec![
            ("{upload}-user-avatars", "/api/user-avatars"),
            ("{upload}-file_storage", "/api/file_storage"),
            ("{upload}-temp_files", "/api/temp_files"),
            ("{upload}-backup-data", "/api/backup-data"),
        ];

        for (dirname, expected_route) in test_cases {
            let entry = create_test_dir(temp_dir.path(), dirname);
            let route_params = RouteParams::new(
                "/api",
                &entry,
                Config::default().with_protect(false),
                &ConfigStore::default(),
            );
            let result = RouteUpload::try_parse(route_params);

            match result {
                Route::Upload(route_upload) => {
                    assert_eq!(route_upload.route, expected_route);
                    assert!(!route_upload.is_temporary);
                    assert!(!route_upload.is_protected);
                }
                _ => panic!("Expected Route::Upload for {}", dirname),
            }
        }
    }

    #[test]
    fn test_try_parse_non_upload_directory() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "regular_folder");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::None => {
                // This is expected for non-upload directories
            }
            _ => panic!("Expected Route::None for non-upload directory"),
        }
    }

    #[test]
    fn test_try_parse_partial_upload_match() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "upload_folder");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::None => {
                // Should not match "upload_folder" as it doesn't match the pattern
            }
            _ => panic!("Expected Route::None for partial upload match"),
        }
    }

    #[test]
    fn test_try_parse_malformed_upload_patterns() {
        let temp_dir = TempDir::new().unwrap();
        let malformed_patterns = vec![
            "upload{temp}",   // Missing braces around upload
            "{uploads}",      // Wrong name (uploads vs upload)
            "{upload{temp}}", // Nested braces
            "upload-files",   // No braces at all
        ];

        for pattern in malformed_patterns {
            let entry = create_test_dir(temp_dir.path(), pattern);
            let route_params = RouteParams::new(
                "/api",
                &entry,
                Config::default().with_protect(false),
                &ConfigStore::default(),
            );
            let result = RouteUpload::try_parse(route_params);

            match result {
                Route::None => {
                    // Expected for malformed patterns
                }
                _ => panic!("Expected Route::None for malformed pattern: {}", pattern),
            }
        }
    }

    #[test]
    fn test_try_parse_file_path_preservation() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}{temp}-cache");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::Upload(route_upload) => {
                let expected_path = temp_dir
                    .path()
                    .join("{upload}{temp}-cache")
                    .into_os_string();
                assert_eq!(route_upload.path, expected_path);
                assert_eq!(route_upload.route, "/api/cache");
                assert!(route_upload.is_temporary);
            }
            _ => panic!("Expected Route::Upload"),
        }
    }

    #[test]
    fn test_try_parse_regex_pattern_validation() {
        let temp_dir = TempDir::new().unwrap();

        // Valid patterns that should match
        let valid_patterns = vec![
            "{upload}",
            "${upload}",
            "{upload}{temp}",
            "${upload}{temp}",
            "{upload}-files",
            "${upload}-secure",
            "{upload}{temp}-temp",
            "${upload}{temp}-secure-temp",
        ];

        for pattern in valid_patterns {
            let entry = create_test_dir(temp_dir.path(), pattern);
            let route_params = RouteParams::new(
                "/test",
                &entry,
                Config::default().with_protect(false),
                &ConfigStore::default(),
            );
            let result = RouteUpload::try_parse(route_params);

            match result {
                Route::Upload(_) => {
                    // Expected for valid patterns
                }
                _ => panic!("Expected Route::Upload for valid pattern: {}", pattern),
            }
        }
    }

    #[test]
    fn test_try_parse_edge_case_empty_custom_route() {
        let temp_dir = TempDir::new().unwrap();
        let entry = create_test_dir(temp_dir.path(), "{upload}-");
        let route_params = RouteParams::new(
            "/api",
            &entry,
            Config::default().with_protect(false),
            &ConfigStore::default(),
        );

        let result = RouteUpload::try_parse(route_params);

        match result {
            Route::None => {
                // The regex pattern requires at least one character after the hyphen
                // So "{upload}-" (with empty string after hyphen) should not match
            }
            _ => panic!("Expected Route::None for empty custom route"),
        }
    }

    #[test]
    fn test_try_parse_combination_matrix() {
        let temp_dir = TempDir::new().unwrap();

        // Test all combinations of protection, temporary, and custom routes
        let combinations = vec![
            ("{upload}", false, false, "/api/upload"),
            ("${upload}", true, false, "/api/upload"),
            ("{upload}{temp}", false, true, "/api/upload"),
            ("${upload}{temp}", true, true, "/api/upload"),
            ("{upload}-custom", false, false, "/api/custom"),
            ("${upload}-custom", true, false, "/api/custom"),
            ("{upload}{temp}-custom", false, true, "/api/custom"),
            ("${upload}{temp}-custom", true, true, "/api/custom"),
        ];

        for (pattern, expected_protected, expected_temporary, expected_route) in combinations {
            let entry = create_test_dir(temp_dir.path(), pattern);
            let route_params = RouteParams::new(
                "/api",
                &entry,
                Config::default().with_protect(false),
                &ConfigStore::default(),
            );
            let result = RouteUpload::try_parse(route_params);

            match result {
                Route::Upload(route_upload) => {
                    assert_eq!(
                        route_upload.route, expected_route,
                        "Route mismatch for {}",
                        pattern
                    );
                    assert_eq!(
                        route_upload.is_protected, expected_protected,
                        "Protection mismatch for {}",
                        pattern
                    );
                    assert_eq!(
                        route_upload.is_temporary, expected_temporary,
                        "Temporary mismatch for {}",
                        pattern
                    );
                }
                _ => panic!("Expected Route::Upload for pattern: {}", pattern),
            }
        }
    }
}
