use sqlx::PgPool;
use uuid::Uuid;
use crate::{
    error::{ApiError, Result},
    models::*,
    utils::{query::QueryBuilder, sanitize::strip_html_tags},
};

/// Generic database operations for common patterns
pub struct DbOperations;

impl DbOperations {
    /// Generic list operation with pagination
    pub async fn list_paginated<T>(
        pool: &PgPool,
        table_name: &str,
        offset: i64,
        limit: i64,
        active_only: bool,
        additional_conditions: Option<&str>,
    ) -> Result<(Vec<T>, i64)>
    where
        T: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let active_condition = if active_only {
            " AND is_active = true"
        } else {
            ""
        };

        let additional_cond = additional_conditions.unwrap_or("");
        
        let base_query = format!(
            "SELECT * FROM {} WHERE 1=1{}{} ORDER BY created_at DESC",
            table_name, active_condition, additional_cond
        );
        
        let count_query = format!(
            "SELECT COUNT(*) as count FROM {} WHERE 1=1{}{}",
            table_name, active_condition, additional_cond
        );

        let query_with_pagination = format!("{} LIMIT {} OFFSET {}", base_query, limit, offset);
        
        let (items, total) = tokio::try_join!(
            QueryBuilder::fetch_all::<T>(pool, &query_with_pagination),
            QueryBuilder::fetch_one::<(i64,)>(pool, &count_query)
        )?;
        
        Ok((items, total.0))
    }

    /// Generic get by ID operation 
    pub async fn get_by_id<T>(
        pool: &PgPool,
        table_name: &str,
        id: &Uuid,
    ) -> Result<Option<T>>
    where
        T: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let query = format!("SELECT * FROM {} WHERE id = $1", table_name);
        sqlx::query_as(&query)
            .bind(id)
            .fetch_optional(pool)
            .await
            .map_err(ApiError::DatabaseError)
    }

    /// Generic get by ID operation for bulletins specifically
    pub async fn get_bulletin_by_id(
        pool: &PgPool,
        id: &Uuid,
    ) -> Result<Option<Bulletin>> {
        sqlx::query_as!(
            Bulletin,
            "SELECT id, title, date, url, pdf_url, is_active, pdf_file, sabbath_school, divine_worship, 
             scripture_reading, sunset, cover_image, pdf_path, created_at, updated_at 
             FROM bulletins WHERE id = $1",
            id
        )
        .fetch_optional(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Generic get by ID operation for events specifically  
    pub async fn get_event_by_id(
        pool: &PgPool,
        id: &Uuid,
    ) -> Result<Option<Event>> {
        sqlx::query_as!(Event, "SELECT * FROM events WHERE id = $1", id)
            .fetch_optional(pool)
            .await
            .map_err(ApiError::DatabaseError)
    }

    /// Delete bulletin by ID
    pub async fn delete_bulletin_by_id(
        pool: &PgPool,
        id: &Uuid,
    ) -> Result<()> {
        let result = sqlx::query!("DELETE FROM bulletins WHERE id = $1", id)
            .execute(pool)
            .await
            .map_err(ApiError::DatabaseError)?;
        
        if result.rows_affected() == 0 {
            return Err(ApiError::NotFound("Bulletin not found".to_string()));
        }
        
        Ok(())
    }

    /// Generic delete by ID operation
    pub async fn delete_by_id(
        pool: &PgPool,
        table_name: &str,
        id: &Uuid,
    ) -> Result<()> {
        let query = format!("DELETE FROM {} WHERE id = $1", table_name);
        let result = sqlx::query(&query)
            .bind(id)
            .execute(pool)
            .await
            .map_err(ApiError::DatabaseError)?;
        
        if result.rows_affected() == 0 {
            return Err(ApiError::NotFound(format!("Record not found in {}", table_name)));
        }
        
        Ok(())
    }

    /// Delete event by ID
    pub async fn delete_event_by_id(
        pool: &PgPool,
        id: &Uuid,
    ) -> Result<()> {
        let result = sqlx::query!("DELETE FROM events WHERE id = $1", id)
            .execute(pool)
            .await
            .map_err(ApiError::DatabaseError)?;
        
        if result.rows_affected() == 0 {
            return Err(ApiError::NotFound("Event not found".to_string()));
        }
        
        Ok(())
    }

    /// Generic active/featured filtering
    pub async fn get_active<T>(
        pool: &PgPool,
        table_name: &str,
        limit: Option<i64>,
    ) -> Result<Vec<T>>
    where
        T: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let limit_clause = limit.map(|l| format!(" LIMIT {}", l)).unwrap_or_default();
        let query = format!(
            "SELECT * FROM {} WHERE is_active = true ORDER BY created_at DESC{}",
            table_name, limit_clause
        );
        QueryBuilder::fetch_all(pool, &query).await
    }

    /// Generic current item (for bulletins, etc.)
    pub async fn get_current<T>(
        pool: &PgPool,
        table_name: &str,
        date_column: &str,
    ) -> Result<Option<T>>
    where
        T: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let query = format!(
            "SELECT * FROM {} WHERE is_active = true AND {} <= (NOW() AT TIME ZONE 'America/New_York')::date ORDER BY {} DESC LIMIT 1",
            table_name, date_column, date_column
        );
        QueryBuilder::fetch_optional(pool, &query).await
    }

    /// Generic next item (for bulletins, etc.)
    pub async fn get_next<T>(
        pool: &PgPool,
        table_name: &str,
        date_column: &str,
    ) -> Result<Option<T>>
    where
        T: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let query = format!(
            "SELECT * FROM {} WHERE is_active = true AND {} > (NOW() AT TIME ZONE 'America/New_York')::date ORDER BY {} ASC LIMIT 1",
            table_name, date_column, date_column
        );
        QueryBuilder::fetch_optional(pool, &query).await
    }
}

/// Specialized operations for events
pub struct EventOperations;

impl EventOperations {
    /// Get upcoming events
    pub async fn get_upcoming(pool: &PgPool, limit: i64) -> Result<Vec<Event>> {
        sqlx::query_as!(
            Event,
            "SELECT * FROM events WHERE start_time > NOW() ORDER BY start_time ASC LIMIT $1",
            limit
        )
        .fetch_all(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Get featured events
    pub async fn get_featured(pool: &PgPool, limit: i64) -> Result<Vec<Event>> {
        sqlx::query_as!(
            Event,
            "SELECT * FROM events WHERE is_featured = true AND start_time > NOW() ORDER BY start_time ASC LIMIT $1",
            limit
        )
        .fetch_all(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Create event with sanitization
    pub async fn create(pool: &PgPool, req: CreateEventRequest) -> Result<Event> {
        let sanitized_description = strip_html_tags(&req.description);
        let normalized_recurring_type = req.recurring_type.as_ref()
            .map(|rt| crate::utils::validation::normalize_recurring_type(rt));
        
        sqlx::query_as!(
            Event,
            r#"
            INSERT INTO events (
                id, title, description, start_time, end_time, location, 
                location_url, category, is_featured, recurring_type
            ) VALUES (
                gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9
            ) RETURNING *"#,
            req.title,
            sanitized_description,
            req.start_time,
            req.end_time,
            req.location,
            req.location_url,
            req.category,
            req.is_featured.unwrap_or(false),
            normalized_recurring_type,
        )
        .fetch_one(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Update event
    pub async fn update(pool: &PgPool, id: &Uuid, req: CreateEventRequest) -> Result<Event> {
        let sanitized_description = strip_html_tags(&req.description);
        let normalized_recurring_type = req.recurring_type.as_ref()
            .map(|rt| crate::utils::validation::normalize_recurring_type(rt));
        
        sqlx::query_as!(
            Event,
            r#"
            UPDATE events SET 
                title = $2, description = $3, start_time = $4, end_time = $5,
                location = $6, location_url = $7, category = $8, 
                is_featured = $9, recurring_type = $10, updated_at = NOW()
            WHERE id = $1 RETURNING *"#,
            id,
            req.title,
            sanitized_description,
            req.start_time,
            req.end_time,
            req.location,
            req.location_url,
            req.category,
            req.is_featured.unwrap_or(false),
            normalized_recurring_type,
        )
        .fetch_one(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Submit pending event
    pub async fn submit_pending(pool: &PgPool, req: SubmitEventRequest) -> Result<PendingEvent> {
        let sanitized_description = strip_html_tags(&req.description);
        
        sqlx::query_as!(
            PendingEvent,
            r#"
            INSERT INTO pending_events (
                id, title, description, start_time, end_time, location,
                location_url, category, is_featured, recurring_type,
                bulletin_week, submitter_email, image, thumbnail
            ) VALUES (
                gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
            ) RETURNING *"#,
            req.title,
            sanitized_description,
            req.start_time,
            req.end_time,
            req.location,
            req.location_url,
            req.category,
            req.is_featured.unwrap_or(false),
            req.recurring_type,
            req.bulletin_week,
            req.submitter_email,
            req.image,
            req.thumbnail,
        )
        .fetch_one(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }
}

/// Specialized operations for bulletins
pub struct BulletinOperations;

impl BulletinOperations {
    /// Get current bulletin
    pub async fn get_current(pool: &PgPool) -> Result<Option<Bulletin>> {
        DbOperations::get_current(pool, "bulletins", "date").await
    }

    /// Get next bulletin
    pub async fn get_next(pool: &PgPool) -> Result<Option<Bulletin>> {
        DbOperations::get_next(pool, "bulletins", "date").await
    }

    /// List bulletins with pagination
    pub async fn list_paginated(
        pool: &PgPool,
        offset: i64,
        limit: i64,
        active_only: bool,
    ) -> Result<(Vec<Bulletin>, i64)> {
        // Use custom query for bulletins to order by date instead of created_at
        let active_condition = if active_only {
            " AND is_active = true"
        } else {
            ""
        };
        
        let base_query = format!(
            "SELECT * FROM bulletins WHERE 1=1{} ORDER BY date DESC",
            active_condition
        );
        
        let count_query = format!(
            "SELECT COUNT(*) as count FROM bulletins WHERE 1=1{}",
            active_condition
        );

        let query_with_pagination = format!("{} LIMIT {} OFFSET {}", base_query, limit, offset);
        
        let (items, total) = tokio::try_join!(
            crate::utils::query::QueryBuilder::fetch_all::<Bulletin>(pool, &query_with_pagination),
            crate::utils::query::QueryBuilder::fetch_one::<(i64,)>(pool, &count_query)
        )?;
        
        Ok((items, total.0))
    }

    /// Create bulletin
    pub async fn create(pool: &PgPool, req: CreateBulletinRequest) -> Result<Bulletin> {
        sqlx::query_as!(
            Bulletin,
            r#"
            INSERT INTO bulletins (
                id, title, date, url, cover_image, sabbath_school,
                divine_worship, scripture_reading, sunset, is_active
            ) VALUES (
                gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9
            ) RETURNING id, title, date, url, pdf_url, is_active, pdf_file,
                        sabbath_school, divine_worship, scripture_reading, sunset,
                        cover_image, pdf_path, created_at, updated_at"#,
            req.title,
            req.date,
            req.url,
            req.cover_image,
            req.sabbath_school,
            req.divine_worship,
            req.scripture_reading,
            req.sunset,
            req.is_active.unwrap_or(true),
        )
        .fetch_one(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }
}

/// Specialized operations for bible verses
pub struct BibleVerseOperations;

impl BibleVerseOperations {
    /// Get random active verse
    pub async fn get_random(pool: &PgPool) -> Result<Option<BibleVerse>> {
        sqlx::query_as!(
            BibleVerse,
            "SELECT * FROM bible_verses WHERE is_active = true ORDER BY RANDOM() LIMIT 1"
        )
        .fetch_optional(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Parse verse range format (e.g., "John 3:16-18" or "2 Peter 1:20-21")
    fn parse_verse_range(query: &str) -> Option<(String, i32, i32)> {
        // Look for pattern: "Book Chapter:StartVerse-EndVerse"
        if let Some(dash_pos) = query.rfind('-') {
            let before_dash = &query[..dash_pos];
            let after_dash = &query[dash_pos + 1..];
            
            // Parse end verse
            if let Ok(end_verse) = after_dash.parse::<i32>() {
                // Find the colon to extract start verse
                if let Some(colon_pos) = before_dash.rfind(':') {
                    let book_chapter = &before_dash[..colon_pos];
                    let start_verse_str = &before_dash[colon_pos + 1..];
                    
                    if let Ok(start_verse) = start_verse_str.parse::<i32>() {
                        return Some((book_chapter.to_string(), start_verse, end_verse));
                    }
                }
            }
        }
        None
    }

    /// Search verses by text or reference (supports comma-separated references and verse ranges)
    pub async fn search(pool: &PgPool, query_text: &str, limit: i64) -> Result<Vec<BibleVerse>> {
        // Check if query contains comma (multiple references)
        if query_text.contains(',') {
            let mut all_verses = Vec::new();
            let references: Vec<&str> = query_text.split(',').map(|s| s.trim()).collect();
            
            for reference in references {
                if !reference.is_empty() {
                    let verses = Self::search_single_reference(pool, reference, limit).await?;
                    all_verses.extend(verses);
                }
            }
            
            // Remove duplicates and apply limit
            all_verses.sort_by(|a, b| Self::sort_bible_references(&a.reference, &b.reference));
            all_verses.dedup_by(|a, b| a.id == b.id);
            all_verses.truncate(limit as usize);
            
            Ok(all_verses)
        } else {
            Self::search_single_reference(pool, query_text, limit).await
        }
    }

    /// Search a single reference which may be a range or simple pattern
    async fn search_single_reference(pool: &PgPool, query_text: &str, limit: i64) -> Result<Vec<BibleVerse>> {
        // Check if this is a verse range
        if let Some((book_chapter, start_verse, end_verse)) = Self::parse_verse_range(query_text) {
            let mut all_verses = Vec::new();
            
            // Query for each verse in the range
            for verse_num in start_verse..=end_verse {
                let reference_pattern = format!("{}:{}", book_chapter, verse_num);
                let verses = sqlx::query_as!(
                    BibleVerse,
                    r#"
                    SELECT * FROM bible_verses 
                    WHERE is_active = true 
                    AND reference ILIKE $1"#,
                    reference_pattern
                )
                .fetch_all(pool)
                .await
                .map_err(ApiError::DatabaseError)?;
                
                all_verses.extend(verses);
            }
            
            // Sort by verse order and apply limit
            all_verses.sort_by(|a, b| Self::sort_bible_references(&a.reference, &b.reference));
            all_verses.truncate(limit as usize);
            
            Ok(all_verses)
        } else {
            // Single reference search (existing logic)
            let search_pattern = format!("%{}%", query_text);
            sqlx::query_as!(
                BibleVerse,
                r#"
                SELECT * FROM bible_verses 
                WHERE is_active = true 
                AND (reference ILIKE $1 OR text ILIKE $1)
                ORDER BY reference 
                LIMIT $2"#,
                search_pattern,
                limit
            )
            .fetch_all(pool)
            .await
            .map_err(ApiError::DatabaseError)
        }
    }

    /// Sort bible references in proper order (by book, chapter, verse)
    fn sort_bible_references(a: &str, b: &str) -> std::cmp::Ordering {
        // Simple comparison for now - could be enhanced with proper book ordering
        a.cmp(b)
    }
}

/// Specialized operations for schedules
pub struct ScheduleOperations;

impl ScheduleOperations {
    /// Get schedule by date
    pub async fn get_by_date(pool: &PgPool, date: chrono::NaiveDate) -> Result<Option<Schedule>> {
        sqlx::query_as!(
            Schedule,
            "SELECT * FROM schedule WHERE date = $1",
            date
        )
        .fetch_optional(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }

    /// Get schedule for date range
    pub async fn get_for_range(
        pool: &PgPool,
        start_date: chrono::NaiveDate,
        end_date: chrono::NaiveDate,
    ) -> Result<Vec<Schedule>> {
        sqlx::query_as!(
            Schedule,
            "SELECT * FROM schedule WHERE date BETWEEN $1 AND $2 ORDER BY date",
            start_date,
            end_date
        )
        .fetch_all(pool)
        .await
        .map_err(ApiError::DatabaseError)
    }
}