use axum::{
    extract::{Path, Query, State},
    response::{Html, IntoResponse},
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebApp {
    pub id: Uuid,
    pub name: String,
    pub slug: String,
    pub description: Option<String>,
    pub template: WebAppTemplate,
    pub status: WebAppStatus,
    pub config: WebAppConfig,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum WebAppTemplate {
    #[default]
    Blank,
    Landing,
    Dashboard,
    Form,
    Portal,
    Custom(String),
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum WebAppStatus {
    #[default]
    Draft,
    Published,
    Archived,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WebAppConfig {
    pub theme: String,
    pub layout: String,
    pub auth_required: bool,
    pub custom_domain: Option<String>,
    pub meta_tags: HashMap<String, String>,
    pub scripts: Vec<String>,
    pub styles: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAppPage {
    pub id: Uuid,
    pub app_id: Uuid,
    pub path: String,
    pub title: String,
    pub content: String,
    pub layout: Option<String>,
    pub is_index: bool,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAppComponent {
    pub id: Uuid,
    pub app_id: Uuid,
    pub name: String,
    pub component_type: ComponentType,
    pub props: serde_json::Value,
    pub children: Vec<Uuid>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ComponentType {
    Container,
    Text,
    Image,
    Button,
    Form,
    Input,
    Table,
    Chart,
    Custom(String),
}

pub struct WebaState {
    apps: RwLock<HashMap<Uuid, WebApp>>,
    pages: RwLock<HashMap<Uuid, WebAppPage>>,
    _components: RwLock<HashMap<Uuid, WebAppComponent>>,
}

impl WebaState {
    pub fn new() -> Self {
        Self {
            apps: RwLock::new(HashMap::new()),
            pages: RwLock::new(HashMap::new()),
            _components: RwLock::new(HashMap::new()),
        }
    }
}

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

#[derive(Debug, Deserialize)]
pub struct CreateAppRequest {
    pub name: String,
    pub description: Option<String>,
    pub template: Option<WebAppTemplate>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateAppRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub status: Option<WebAppStatus>,
    pub config: Option<WebAppConfig>,
}

#[derive(Debug, Deserialize)]
pub struct CreatePageRequest {
    pub path: String,
    pub title: String,
    pub content: String,
    pub layout: Option<String>,
    pub is_index: bool,
}

#[derive(Debug, Deserialize)]
pub struct ListQuery {
    pub limit: Option<usize>,
    pub offset: Option<usize>,
    pub status: Option<String>,
}

pub fn configure_routes(state: Arc<WebaState>) -> Router {
    Router::new()
        .route("/apps", get(list_apps).post(create_app))
        .route("/apps/:id", get(get_app).put(update_app).delete(delete_app))
        .route("/apps/:id/pages", get(list_pages).post(create_page))
        .route(
            "/apps/:id/pages/:page_id",
            get(get_page).put(update_page).delete(delete_page),
        )
        .route("/apps/:id/publish", post(publish_app))
        .route("/apps/:id/preview", get(preview_app))
        .route("/render/:slug", get(render_app))
        .route("/render/:slug/*path", get(render_page))
        .with_state(state)
}

async fn list_apps(
    State(state): State<Arc<WebaState>>,
    Query(query): Query<ListQuery>,
) -> Json<Vec<WebApp>> {
    let apps = state.apps.read().await;
    let mut result: Vec<WebApp> = apps.values().cloned().collect();

    if let Some(status) = query.status {
        result.retain(|app| {
            matches!(
                (&app.status, status.as_str()),
                (WebAppStatus::Draft, "draft")
                    | (WebAppStatus::Published, "published")
                    | (WebAppStatus::Archived, "archived")
            )
        });
    }

    result.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));

    let offset = query.offset.unwrap_or(0);
    let limit = query.limit.unwrap_or(50);
    let result: Vec<WebApp> = result.into_iter().skip(offset).take(limit).collect();

    Json(result)
}

async fn create_app(
    State(state): State<Arc<WebaState>>,
    Json(req): Json<CreateAppRequest>,
) -> Json<WebApp> {
    let now = chrono::Utc::now();
    let id = Uuid::new_v4();
    let slug = slugify(&req.name);

    let app = WebApp {
        id,
        name: req.name,
        slug,
        description: req.description,
        template: req.template.unwrap_or_default(),
        status: WebAppStatus::Draft,
        config: WebAppConfig::default(),
        created_at: now,
        updated_at: now,
    };

    let mut apps = state.apps.write().await;
    apps.insert(id, app.clone());

    Json(app)
}

async fn get_app(
    State(state): State<Arc<WebaState>>,
    Path(id): Path<Uuid>,
) -> Result<Json<WebApp>, axum::http::StatusCode> {
    let apps = state.apps.read().await;
    apps.get(&id)
        .cloned()
        .map(Json)
        .ok_or(axum::http::StatusCode::NOT_FOUND)
}

async fn update_app(
    State(state): State<Arc<WebaState>>,
    Path(id): Path<Uuid>,
    Json(req): Json<UpdateAppRequest>,
) -> Result<Json<WebApp>, axum::http::StatusCode> {
    let mut apps = state.apps.write().await;

    let app = apps.get_mut(&id).ok_or(axum::http::StatusCode::NOT_FOUND)?;

    if let Some(name) = req.name {
        app.name.clone_from(&name);
        app.slug = slugify(&name);
    }
    if let Some(description) = req.description {
        app.description = Some(description);
    }
    if let Some(status) = req.status {
        app.status = status;
    }
    if let Some(config) = req.config {
        app.config = config;
    }
    app.updated_at = chrono::Utc::now();

    Ok(Json(app.clone()))
}

async fn delete_app(
    State(state): State<Arc<WebaState>>,
    Path(id): Path<Uuid>,
) -> axum::http::StatusCode {
    let mut apps = state.apps.write().await;
    let mut pages = state.pages.write().await;

    pages.retain(|_, page| page.app_id != id);

    if apps.remove(&id).is_some() {
        axum::http::StatusCode::NO_CONTENT
    } else {
        axum::http::StatusCode::NOT_FOUND
    }
}

async fn list_pages(
    State(state): State<Arc<WebaState>>,
    Path(app_id): Path<Uuid>,
) -> Json<Vec<WebAppPage>> {
    let pages = state.pages.read().await;
    let result: Vec<WebAppPage> = pages
        .values()
        .filter(|p| p.app_id == app_id)
        .cloned()
        .collect();
    Json(result)
}

async fn create_page(
    State(state): State<Arc<WebaState>>,
    Path(app_id): Path<Uuid>,
    Json(req): Json<CreatePageRequest>,
) -> Result<Json<WebAppPage>, axum::http::StatusCode> {
    let apps = state.apps.read().await;
    if !apps.contains_key(&app_id) {
        return Err(axum::http::StatusCode::NOT_FOUND);
    }
    drop(apps);

    let now = chrono::Utc::now();
    let id = Uuid::new_v4();

    let page = WebAppPage {
        id,
        app_id,
        path: req.path,
        title: req.title,
        content: req.content,
        layout: req.layout,
        is_index: req.is_index,
        created_at: now,
        updated_at: now,
    };

    let mut pages = state.pages.write().await;
    pages.insert(id, page.clone());

    Ok(Json(page))
}

async fn get_page(
    State(state): State<Arc<WebaState>>,
    Path((app_id, page_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<WebAppPage>, axum::http::StatusCode> {
    let pages = state.pages.read().await;
    pages
        .get(&page_id)
        .filter(|p| p.app_id == app_id)
        .cloned()
        .map(Json)
        .ok_or(axum::http::StatusCode::NOT_FOUND)
}

async fn update_page(
    State(state): State<Arc<WebaState>>,
    Path((app_id, page_id)): Path<(Uuid, Uuid)>,
    Json(req): Json<CreatePageRequest>,
) -> Result<Json<WebAppPage>, axum::http::StatusCode> {
    let mut pages = state.pages.write().await;

    let page = pages
        .get_mut(&page_id)
        .filter(|p| p.app_id == app_id)
        .ok_or(axum::http::StatusCode::NOT_FOUND)?;

    page.path = req.path;
    page.title = req.title;
    page.content = req.content;
    page.layout = req.layout;
    page.is_index = req.is_index;
    page.updated_at = chrono::Utc::now();

    Ok(Json(page.clone()))
}

async fn delete_page(
    State(state): State<Arc<WebaState>>,
    Path((app_id, page_id)): Path<(Uuid, Uuid)>,
) -> axum::http::StatusCode {
    let mut pages = state.pages.write().await;

    let exists = pages
        .get(&page_id)
        .map(|p| p.app_id == app_id)
        .unwrap_or(false);

    if exists {
        pages.remove(&page_id);
        axum::http::StatusCode::NO_CONTENT
    } else {
        axum::http::StatusCode::NOT_FOUND
    }
}

async fn publish_app(
    State(state): State<Arc<WebaState>>,
    Path(id): Path<Uuid>,
) -> Result<Json<WebApp>, axum::http::StatusCode> {
    let mut apps = state.apps.write().await;
    let app = apps.get_mut(&id).ok_or(axum::http::StatusCode::NOT_FOUND)?;

    app.status = WebAppStatus::Published;
    app.updated_at = chrono::Utc::now();

    Ok(Json(app.clone()))
}

async fn preview_app(
    State(state): State<Arc<WebaState>>,
    Path(id): Path<Uuid>,
) -> Result<Html<String>, axum::http::StatusCode> {
    let apps = state.apps.read().await;
    let app = apps.get(&id).ok_or(axum::http::StatusCode::NOT_FOUND)?;

    let pages = state.pages.read().await;
    let index_page = pages.values().find(|p| p.app_id == id && p.is_index);

    let content = index_page
        .map(|p| p.content.clone())
        .unwrap_or_else(|| "<p>No content yet</p>".to_string());

    let html = render_html(app, &content);
    Ok(Html(html))
}

async fn render_app(
    State(state): State<Arc<WebaState>>,
    Path(slug): Path<String>,
) -> Result<impl IntoResponse, axum::http::StatusCode> {
    let apps = state.apps.read().await;
    let app = apps
        .values()
        .find(|a| a.slug == slug && matches!(a.status, WebAppStatus::Published))
        .ok_or(axum::http::StatusCode::NOT_FOUND)?
        .clone();
    drop(apps);

    let pages = state.pages.read().await;
    let index_page = pages.values().find(|p| p.app_id == app.id && p.is_index);

    let content = index_page
        .map(|p| p.content.clone())
        .unwrap_or_else(|| "<p>Page not found</p>".to_string());

    let html = render_html(&app, &content);
    Ok(Html(html))
}

async fn render_page(
    State(state): State<Arc<WebaState>>,
    Path((slug, path)): Path<(String, String)>,
) -> Result<impl IntoResponse, axum::http::StatusCode> {
    let apps = state.apps.read().await;
    let app = apps
        .values()
        .find(|a| a.slug == slug && matches!(a.status, WebAppStatus::Published))
        .ok_or(axum::http::StatusCode::NOT_FOUND)?
        .clone();
    drop(apps);

    let normalized_path = format!("/{}", path.trim_start_matches('/'));

    let pages = state.pages.read().await;
    let page = pages
        .values()
        .find(|p| p.app_id == app.id && p.path == normalized_path);

    let content = page
        .map(|p| p.content.clone())
        .unwrap_or_else(|| "<p>Page not found</p>".to_string());

    let html = render_html(&app, &content);
    Ok(Html(html))
}

fn render_html(app: &WebApp, content: &str) -> String {
    let meta_tags: String = app
        .config
        .meta_tags
        .iter()
        .map(|(k, v)| format!("<meta name=\"{k}\" content=\"{v}\">"))
        .collect::<Vec<_>>()
        .join("\n    ");

    let scripts: String = app
        .config
        .scripts
        .iter()
        .map(|s| format!("<script src=\"{s}\"></script>"))
        .collect::<Vec<_>>()
        .join("\n    ");

    let styles: String = app
        .config
        .styles
        .iter()
        .map(|s| format!("<link rel=\"stylesheet\" href=\"{s}\">"))
        .collect::<Vec<_>>()
        .join("\n    ");

    format!(
        r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{}</title>
    {}
    {}
    <style>
        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }}
    </style>
</head>
<body>
    {}
    {}
</body>
</html>"#,
        app.name, meta_tags, styles, content, scripts
    )
}

pub fn slugify(s: &str) -> String {
    s.to_lowercase()
        .chars()
        .map(|c| if c.is_alphanumeric() { c } else { '-' })
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<_>>()
        .join("-")
}

pub fn init() {
    log::info!("WEBA module initialized");
}

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

    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
    pub enum BrowserType {
        #[default]
        Chrome,
        Firefox,
        Safari,
        Edge,
    }

    impl BrowserType {
        pub const fn browser_name(self) -> &'static str {
            match self {
                Self::Chrome => "chrome",
                Self::Firefox => "firefox",
                Self::Safari => "safari",
                Self::Edge => "MicrosoftEdge",
            }
        }
    }

    #[derive(Debug, Clone)]
    pub struct BrowserConfig {
        pub browser_type: BrowserType,
        pub debug_port: u16,
        pub headless: bool,
        pub window_width: u32,
        pub window_height: u32,
        pub timeout: Duration,
    }

    impl Default for BrowserConfig {
        fn default() -> Self {
            Self {
                browser_type: BrowserType::Chrome,
                debug_port: 9222,
                headless: true,
                window_width: 1920,
                window_height: 1080,
                timeout: Duration::from_secs(30),
            }
        }
    }

    impl BrowserConfig {
        pub fn new() -> Self {
            Self::default()
        }

        #[cfg(test)]
        pub const fn with_browser(mut self, browser: BrowserType) -> Self {
            self.browser_type = browser;
            self
        }

        pub const fn with_debug_port(mut self, port: u16) -> Self {
            self.debug_port = port;
            self
        }

        pub const fn headless(mut self, headless: bool) -> Self {
            self.headless = headless;
            self
        }

        pub const fn with_window_size(mut self, width: u32, height: u32) -> Self {
            self.window_width = width;
            self.window_height = height;
            self
        }

        pub const fn with_timeout(mut self, timeout: Duration) -> Self {
            self.timeout = timeout;
            self
        }
    }

    #[derive(Debug, Clone)]
    pub struct E2EConfig {
        browser: BrowserType,
        headless: bool,
        timeout: Duration,
        pub window_width: u32,
        pub window_height: u32,
        pub screenshot_on_failure: bool,
        screenshot_dir: String,
    }

    impl E2EConfig {
        pub fn browser(&self) -> BrowserType {
            self.browser
        }
        pub fn headless(&self) -> bool {
            self.headless
        }
        pub fn timeout(&self) -> Duration {
            self.timeout
        }
        pub fn screenshot_dir(&self) -> &str {
            &self.screenshot_dir
        }
    }

    impl Default for E2EConfig {
        fn default() -> Self {
            Self {
                browser: BrowserType::Chrome,
                headless: true,
                timeout: Duration::from_secs(30),
                window_width: 1920,
                window_height: 1080,
                screenshot_on_failure: true,
                screenshot_dir: "./test-screenshots".to_string(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Locator {
        Css(String),
        XPath(String),
        Id(String),
    }

    impl Locator {
        pub fn css(selector: &str) -> Self {
            Self::Css(selector.to_string())
        }

        pub fn xpath(expr: &str) -> Self {
            Self::XPath(expr.to_string())
        }

        pub fn id(id: &str) -> Self {
            Self::Id(id.to_string())
        }

        pub fn as_selector(&self) -> &str {
            match self {
                Self::Css(s) | Self::XPath(s) | Self::Id(s) => s,
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Action {
        Click(Locator),
        SendKeys(String),
        Pause(Duration),
    }

    impl Action {
        pub fn description(&self) -> String {
            match self {
                Self::Click(loc) => format!("click on {}", loc.as_selector()),
                Self::SendKeys(text) => format!("send keys: {text}"),
                Self::Pause(dur) => format!("pause for {dur:?}"),
            }
        }
    }

    pub struct ActionChain {
        actions: Vec<Action>,
    }

    impl ActionChain {
        pub const fn new() -> Self {
            Self {
                actions: Vec::new(),
            }
        }

        pub fn click(mut self, locator: Locator) -> Self {
            self.actions.push(Action::Click(locator));
            self
        }

        pub fn send_keys(mut self, text: &str) -> Self {
            self.actions.push(Action::SendKeys(text.to_string()));
            self
        }

        pub fn pause(mut self, duration: Duration) -> Self {
            self.actions.push(Action::Pause(duration));
            self
        }

        pub fn actions(&self) -> &[Action] {
            &self.actions
        }
    }

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

    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
    pub struct Cookie {
        pub name: String,
        pub value: String,
        pub domain: Option<String>,
        pub path: Option<String>,
        pub secure: Option<bool>,
        pub http_only: Option<bool>,
    }

    impl Cookie {
        pub fn new(name: &str, value: &str) -> Self {
            Self {
                name: name.to_string(),
                value: value.to_string(),
                domain: None,
                path: None,
                secure: None,
                http_only: None,
            }
        }

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

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

        pub const fn secure(mut self) -> Self {
            self.secure = Some(true);
            self
        }

        pub const fn http_only(mut self) -> Self {
            self.http_only = Some(true);
            self
        }
    }

    pub struct LoginPage {
        base_url: String,
    }

    impl LoginPage {
        pub fn new(base_url: &str) -> Self {
            Self {
                base_url: base_url.to_string(),
            }
        }

        pub fn base_url(&self) -> &str {
            &self.base_url
        }

        pub fn url_pattern() -> &'static str {
            "/login"
        }

        pub fn email_input() -> Locator {
            Locator::id("email")
        }

        pub fn password_input() -> Locator {
            Locator::id("password")
        }

        pub fn login_button() -> Locator {
            Locator::css("button[type='submit']")
        }

        pub fn error_message() -> Locator {
            Locator::css(".error-message")
        }
    }

    pub struct DashboardPage {
        base_url: String,
    }

    impl DashboardPage {
        pub fn new(base_url: &str) -> Self {
            Self {
                base_url: base_url.to_string(),
            }
        }

        pub fn base_url(&self) -> &str {
            &self.base_url
        }

        pub fn url_pattern() -> &'static str {
            "/dashboard"
        }
    }

    pub struct ChatPage {
        base_url: String,
        bot_name: String,
    }

    impl ChatPage {
        pub fn new(base_url: &str, bot_name: &str) -> Self {
            Self {
                base_url: base_url.to_string(),
                bot_name: bot_name.to_string(),
            }
        }

        pub fn base_url(&self) -> &str {
            &self.base_url
        }

        pub fn bot_name(&self) -> &str {
            &self.bot_name
        }

        pub fn url_pattern() -> &'static str {
            "/chat/"
        }

        pub fn chat_input() -> Locator {
            Locator::id("chat-input")
        }

        pub fn send_button() -> Locator {
            Locator::css("button.send-message")
        }

        pub fn bot_message() -> Locator {
            Locator::css(".message.bot")
        }

        pub fn typing_indicator() -> Locator {
            Locator::css(".typing-indicator")
        }
    }

    pub struct QueuePage {
        base_url: String,
    }

    impl QueuePage {
        pub fn new(base_url: &str) -> Self {
            Self {
                base_url: base_url.to_string(),
            }
        }

        pub fn base_url(&self) -> &str {
            &self.base_url
        }

        pub fn url_pattern() -> &'static str {
            "/queue"
        }

        pub fn queue_panel() -> Locator {
            Locator::css(".queue-panel")
        }

        pub fn queue_count() -> Locator {
            Locator::css(".queue-count")
        }

        pub fn take_next_button() -> Locator {
            Locator::css("button.take-next")
        }
    }

    pub struct BotManagementPage {
        base_url: String,
    }

    impl BotManagementPage {
        pub fn new(base_url: &str) -> Self {
            Self {
                base_url: base_url.to_string(),
            }
        }

        pub fn base_url(&self) -> &str {
            &self.base_url
        }

        pub fn url_pattern() -> &'static str {
            "/admin/bots"
        }
    }



    #[test]
    fn test_browser_config_builder() {
        let config = BrowserConfig::new()
            .with_browser(BrowserType::Firefox)
            .with_debug_port(9333)
            .headless(false)
            .with_window_size(1280, 720)
            .with_timeout(Duration::from_secs(60));

        assert_eq!(config.browser_type, BrowserType::Firefox);
        assert_eq!(config.debug_port, 9333);
        assert!(!config.headless);
        assert_eq!(config.window_width, 1280);
        assert_eq!(config.window_height, 720);
        assert_eq!(config.timeout, Duration::from_secs(60));
    }



    #[test]
    fn test_locator_constructors() {
        let css = Locator::css(".my-class");
        assert!(matches!(css, Locator::Css(_)));
        assert_eq!(css.as_selector(), ".my-class");

        let xpath = Locator::xpath("//div[@id='test']");
        assert!(matches!(xpath, Locator::XPath(_)));
        assert_eq!(xpath.as_selector(), "//div[@id='test']");

        let id = Locator::id("my-id");
        assert!(matches!(id, Locator::Id(_)));
        assert_eq!(id.as_selector(), "my-id");
    }

    #[test]
    fn test_action_chain() {
        let chain = ActionChain::new()
            .click(Locator::id("button"))
            .send_keys("Hello")
            .pause(Duration::from_millis(500));

        assert_eq!(chain.actions().len(), 3);
        for action in chain.actions() {
            let _ = action.description();
        }
    }

    #[test]
    fn test_cookie_builder() {
        let cookie = Cookie::new("session", "abc123")
            .with_domain("example.com")
            .with_path("/")
            .secure()
            .http_only();

        assert_eq!(cookie.name, "session");
        assert_eq!(cookie.value, "abc123");
        assert_eq!(cookie.domain, Some("example.com".to_string()));
        assert!(cookie.secure.unwrap());
        assert!(cookie.http_only.unwrap());
    }

    #[test]
    fn test_login_page_locators() {
        let _ = LoginPage::email_input();
        let _ = LoginPage::password_input();
        let _ = LoginPage::login_button();
        let _ = LoginPage::error_message();
    }

    #[test]
    fn test_chat_page_locators() {
        let _ = ChatPage::chat_input();
        let _ = ChatPage::send_button();
        let _ = ChatPage::bot_message();
        let _ = ChatPage::typing_indicator();
    }

    #[test]
    fn test_queue_page_locators() {
        let _ = QueuePage::queue_panel();
        let _ = QueuePage::queue_count();
        let _ = QueuePage::take_next_button();
    }

    #[test]
    fn test_page_url_patterns() {
        let login = LoginPage::new("http://localhost:4242");
        assert_eq!(LoginPage::url_pattern(), "/login");
        assert_eq!(login.base_url(), "http://localhost:4242");

        let dashboard = DashboardPage::new("http://localhost:4242");
        assert_eq!(DashboardPage::url_pattern(), "/dashboard");
        assert_eq!(dashboard.base_url(), "http://localhost:4242");

        let chat = ChatPage::new("http://localhost:4242", "test-bot");
        assert_eq!(ChatPage::url_pattern(), "/chat/");
        assert_eq!(chat.base_url(), "http://localhost:4242");
        assert_eq!(chat.bot_name(), "test-bot");

        let queue = QueuePage::new("http://localhost:4242");
        assert_eq!(QueuePage::url_pattern(), "/queue");
        assert_eq!(queue.base_url(), "http://localhost:4242");

        let bots = BotManagementPage::new("http://localhost:4242");
        assert_eq!(BotManagementPage::url_pattern(), "/admin/bots");
        assert_eq!(bots.base_url(), "http://localhost:4242");
    }

    #[test]
    fn test_slugify() {
        assert_eq!(slugify("Hello World"), "hello-world");
        assert_eq!(slugify("My  App  Name"), "my-app-name");
        assert_eq!(slugify("Test123"), "test123");
        assert_eq!(slugify("  Spaces  "), "spaces");
    }

    #[test]
    fn test_web_app_template_default() {
        let template = WebAppTemplate::default();
        assert!(matches!(template, WebAppTemplate::Blank));
    }

    #[test]
    fn test_web_app_status_default() {
        let status = WebAppStatus::default();
        assert!(matches!(status, WebAppStatus::Draft));
    }

    #[test]
    fn test_web_app_config_default() {
        let config = WebAppConfig::default();
        assert!(!config.auth_required);
        assert!(config.custom_domain.is_none());
        assert!(config.meta_tags.is_empty());
        assert!(config.scripts.is_empty());
        assert!(config.styles.is_empty());
    }

    #[test]
    fn test_component_types() {
        let container = ComponentType::Container;
        let text = ComponentType::Text;
        let button = ComponentType::Button;
        let custom = ComponentType::Custom("MyWidget".to_string());

        assert!(matches!(container, ComponentType::Container));
        assert!(matches!(text, ComponentType::Text));
        assert!(matches!(button, ComponentType::Button));
        assert!(matches!(custom, ComponentType::Custom(_)));
    }

    #[test]
    fn test_create_app_request() {
        let request = CreateAppRequest {
            name: "My Test App".to_string(),
            description: Some("A test application".to_string()),
            template: Some(WebAppTemplate::Dashboard),
        };

        assert_eq!(request.name, "My Test App");
        assert!(request.description.is_some());
        assert!(matches!(request.template, Some(WebAppTemplate::Dashboard)));
    }

    #[test]
    fn test_create_page_request() {
        let request = CreatePageRequest {
            path: "/about".to_string(),
            title: "About Us".to_string(),
            content: "<h1>About</h1>".to_string(),
            layout: Some("default".to_string()),
            is_index: false,
        };

        assert_eq!(request.path, "/about");
        assert!(!request.is_index);
    }

    #[test]
    fn test_render_html_basic() {
        let app = WebApp {
            id: Uuid::new_v4(),
            name: "Test App".to_string(),
            slug: "test-app".to_string(),
            description: None,
            template: WebAppTemplate::Blank,
            status: WebAppStatus::Published,
            config: WebAppConfig::default(),
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
        };

        let html = render_html(&app, "<p>Hello</p>");
        assert!(html.contains("Test App"));
        assert!(html.contains("<p>Hello</p>"));
        assert!(html.contains("<!DOCTYPE html>"));
    }

    #[test]
    fn test_render_html_with_meta_tags() {
        let mut config = WebAppConfig::default();
        config
            .meta_tags
            .insert("description".to_string(), "A test page".to_string());
        config
            .meta_tags
            .insert("author".to_string(), "Test Author".to_string());

        let app = WebApp {
            id: Uuid::new_v4(),
            name: "Meta Test".to_string(),
            slug: "meta-test".to_string(),
            description: None,
            template: WebAppTemplate::Blank,
            status: WebAppStatus::Published,
            config,
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
        };

        let html = render_html(&app, "");
        assert!(html.contains("meta name="));
    }

    #[test]
    fn test_weba_state_creation() {
        let state = WebaState::new();
        let _ = &state;
    }

    #[test]
    fn test_list_query_defaults() {
        let query = ListQuery {
            limit: None,
            offset: None,
            status: None,
        };

        assert!(query.limit.is_none());
        assert!(query.offset.is_none());
        assert!(query.status.is_none());
    }

    #[test]
    fn test_list_query_with_values() {
        let query = ListQuery {
            limit: Some(10),
            offset: Some(20),
            status: Some("published".to_string()),
        };

        assert_eq!(query.limit, Some(10));
        assert_eq!(query.offset, Some(20));
        assert_eq!(query.status, Some("published".to_string()));
    }
}
