use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
use sqlx::{FromRow, Result};
use std::sync::Arc;
use uuid::Uuid;

/// Database connection pool wrapper
#[derive(Clone)]
pub struct Database {
    pool: Arc<SqlitePool>,
}

impl Database {
    /// Create a new database connection pool
    pub async fn new(database_url: &str) -> Result<Self> {
        let pool = SqlitePoolOptions::new()
            .max_connections(5)
            .connect(database_url)
            .await?;

        // Run migrations
        sqlx::migrate!("./migrations").run(&pool).await?;

        Ok(Self {
            pool: Arc::new(pool),
        })
    }

    /// Get a reference to the connection pool
    pub fn pool(&self) -> &SqlitePool {
        &self.pool
    }
}

/// User model representing a CalDAV user
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct User {
    pub id: String, // UUID as string for SQLite
    pub username: String,
    pub server_url: String,
    pub created_at: DateTime<Utc>,
}

impl User {
    /// Create a new user with generated UUID
    pub fn new(username: String, server_url: String) -> Self {
        Self {
            id: Uuid::new_v4().to_string(),
            username,
            server_url,
            created_at: Utc::now(),
        }
    }
}

/// Session model for user sessions
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Session {
    pub id: String,      // UUID as string
    pub user_id: String, // Foreign key to User
    pub token: String,   // Session token
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub last_accessed: DateTime<Utc>,
}

impl Session {
    /// Create a new session for a user
    pub fn new(user_id: String, token: String, expires_in_hours: i64) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4().to_string(),
            user_id,
            token,
            created_at: now,
            expires_at: now + chrono::Duration::hours(expires_in_hours),
            last_accessed: now,
        }
    }

    /// Check if the session has expired
    pub fn is_expired(&self) -> bool {
        Utc::now() > self.expires_at
    }
}

/// User preferences model
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct UserPreferences {
    pub user_id: String,
    pub calendar_selected_date: Option<String>,
    pub calendar_time_increment: Option<i32>,
    pub calendar_view_mode: Option<String>,
    pub calendar_theme: Option<String>,
    pub calendar_style: Option<String>,
    pub calendar_colors: Option<String>, // JSON string
    pub last_used_calendar: Option<String>,
    pub updated_at: DateTime<Utc>,
}

/// External calendar model
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct ExternalCalendar {
    pub id: i32,
    pub user_id: String,
    pub name: String,
    pub url: String,
    pub color: String,
    pub is_visible: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub last_fetched: Option<DateTime<Utc>>,
}

impl ExternalCalendar {
    /// Create a new external calendar
    pub fn new(user_id: String, name: String, url: String, color: String) -> Self {
        let now = Utc::now();
        Self {
            id: 0, // Will be set by database
            user_id,
            name,
            url,
            color,
            is_visible: true,
            created_at: now,
            updated_at: now,
            last_fetched: None,
        }
    }
}

impl UserPreferences {
    /// Create default preferences for a new user
    pub fn default_for_user(user_id: String) -> Self {
        Self {
            user_id,
            calendar_selected_date: None,
            calendar_time_increment: Some(15),
            calendar_view_mode: Some("month".to_string()),
            calendar_theme: Some("light".to_string()),
            calendar_style: Some("default".to_string()),
            calendar_colors: None,
            last_used_calendar: None,
            updated_at: Utc::now(),
        }
    }
}

/// Repository for User operations
pub struct UserRepository<'a> {
    db: &'a Database,
}

impl<'a> UserRepository<'a> {
    pub fn new(db: &'a Database) -> Self {
        Self { db }
    }

    /// Find or create a user by username and server URL
    pub async fn find_or_create(
        &self,
        username: &str,
        server_url: &str,
    ) -> Result<User> {
        // Try to find existing user
        let existing = sqlx::query_as::<_, User>(
            "SELECT * FROM users WHERE username = ? AND server_url = ?",
        )
        .bind(username)
        .bind(server_url)
        .fetch_optional(self.db.pool())
        .await?;

        if let Some(user) = existing {
            Ok(user)
        } else {
            // Create new user
            let user = User::new(username.to_string(), server_url.to_string());
            
            sqlx::query(
                "INSERT INTO users (id, username, server_url, created_at) VALUES (?, ?, ?, ?)",
            )
            .bind(&user.id)
            .bind(&user.username)
            .bind(&user.server_url)
            .bind(&user.created_at)
            .execute(self.db.pool())
            .await?;

            Ok(user)
        }
    }

    /// Find a user by ID
    pub async fn find_by_id(&self, user_id: &str) -> Result<Option<User>> {
        sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
            .bind(user_id)
            .fetch_optional(self.db.pool())
            .await
    }
}

/// Repository for Session operations
pub struct SessionRepository<'a> {
    db: &'a Database,
}

impl<'a> SessionRepository<'a> {
    pub fn new(db: &'a Database) -> Self {
        Self { db }
    }

    /// Create a new session
    pub async fn create(&self, session: &Session) -> Result<()> {
        sqlx::query(
            "INSERT INTO sessions (id, user_id, token, created_at, expires_at, last_accessed) 
             VALUES (?, ?, ?, ?, ?, ?)",
        )
        .bind(&session.id)
        .bind(&session.user_id)
        .bind(&session.token)
        .bind(&session.created_at)
        .bind(&session.expires_at)
        .bind(&session.last_accessed)
        .execute(self.db.pool())
        .await?;

        Ok(())
    }

    /// Find a session by token and update last_accessed
    pub async fn find_by_token(&self, token: &str) -> Result<Option<Session>> {
        let session = sqlx::query_as::<_, Session>("SELECT * FROM sessions WHERE token = ?")
            .bind(token)
            .fetch_optional(self.db.pool())
            .await?;

        if let Some(ref s) = session {
            if !s.is_expired() {
                // Update last_accessed time
                sqlx::query("UPDATE sessions SET last_accessed = ? WHERE id = ?")
                    .bind(Utc::now())
                    .bind(&s.id)
                    .execute(self.db.pool())
                    .await?;
            }
        }

        Ok(session)
    }

    /// Delete a session (logout)
    pub async fn delete(&self, token: &str) -> Result<()> {
        sqlx::query("DELETE FROM sessions WHERE token = ?")
            .bind(token)
            .execute(self.db.pool())
            .await?;

        Ok(())
    }

    /// Clean up expired sessions
    pub async fn cleanup_expired(&self) -> Result<u64> {
        let result = sqlx::query("DELETE FROM sessions WHERE expires_at < ?")
            .bind(Utc::now())
            .execute(self.db.pool())
            .await?;

        Ok(result.rows_affected())
    }
}

/// Repository for UserPreferences operations
pub struct PreferencesRepository<'a> {
    db: &'a Database,
}

impl<'a> PreferencesRepository<'a> {
    pub fn new(db: &'a Database) -> Self {
        Self { db }
    }

    /// Get user preferences, creating defaults if not exist
    pub async fn get_or_create(&self, user_id: &str) -> Result<UserPreferences> {
        let existing = sqlx::query_as::<_, UserPreferences>(
            "SELECT * FROM user_preferences WHERE user_id = ?",
        )
        .bind(user_id)
        .fetch_optional(self.db.pool())
        .await?;

        if let Some(prefs) = existing {
            Ok(prefs)
        } else {
            // Create default preferences
            let prefs = UserPreferences::default_for_user(user_id.to_string());
            
            sqlx::query(
                "INSERT INTO user_preferences 
                 (user_id, calendar_selected_date, calendar_time_increment, 
                  calendar_view_mode, calendar_theme, calendar_style, calendar_colors, last_used_calendar, updated_at) 
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
            )
            .bind(&prefs.user_id)
            .bind(&prefs.calendar_selected_date)
            .bind(&prefs.calendar_time_increment)
            .bind(&prefs.calendar_view_mode)
            .bind(&prefs.calendar_theme)
            .bind(&prefs.calendar_style)
            .bind(&prefs.calendar_colors)
            .bind(&prefs.last_used_calendar)
            .bind(&prefs.updated_at)
            .execute(self.db.pool())
            .await?;

            Ok(prefs)
        }
    }

    /// Update user preferences
    pub async fn update(&self, prefs: &UserPreferences) -> Result<()> {
        sqlx::query(
            "UPDATE user_preferences 
             SET calendar_selected_date = ?, calendar_time_increment = ?, 
                 calendar_view_mode = ?, calendar_theme = ?, calendar_style = ?,
                 calendar_colors = ?, last_used_calendar = ?, updated_at = ?
             WHERE user_id = ?",
        )
        .bind(&prefs.calendar_selected_date)
        .bind(&prefs.calendar_time_increment)
        .bind(&prefs.calendar_view_mode)
        .bind(&prefs.calendar_theme)
        .bind(&prefs.calendar_style)
        .bind(&prefs.calendar_colors)
        .bind(&prefs.last_used_calendar)
        .bind(Utc::now())
        .bind(&prefs.user_id)
        .execute(self.db.pool())
        .await?;

        Ok(())
    }
}

/// Repository for ExternalCalendar operations
pub struct ExternalCalendarRepository<'a> {
    db: &'a Database,
}

impl<'a> ExternalCalendarRepository<'a> {
    pub fn new(db: &'a Database) -> Self {
        Self { db }
    }

    /// Get all external calendars for a user
    pub async fn get_by_user(&self, user_id: &str) -> Result<Vec<ExternalCalendar>> {
        sqlx::query_as::<_, ExternalCalendar>(
            "SELECT * FROM external_calendars WHERE user_id = ? ORDER BY created_at ASC",
        )
        .bind(user_id)
        .fetch_all(self.db.pool())
        .await
    }

    /// Create a new external calendar
    pub async fn create(&self, calendar: &ExternalCalendar) -> Result<i32> {
        let result = sqlx::query(
            "INSERT INTO external_calendars (user_id, name, url, color, is_visible, created_at, updated_at) 
             VALUES (?, ?, ?, ?, ?, ?, ?)",
        )
        .bind(&calendar.user_id)
        .bind(&calendar.name)
        .bind(&calendar.url)
        .bind(&calendar.color)
        .bind(&calendar.is_visible)
        .bind(&calendar.created_at)
        .bind(&calendar.updated_at)
        .execute(self.db.pool())
        .await?;

        Ok(result.last_insert_rowid() as i32)
    }

    /// Update an external calendar
    pub async fn update(&self, id: i32, calendar: &ExternalCalendar) -> Result<()> {
        sqlx::query(
            "UPDATE external_calendars 
             SET name = ?, url = ?, color = ?, is_visible = ?, updated_at = ?
             WHERE id = ? AND user_id = ?",
        )
        .bind(&calendar.name)
        .bind(&calendar.url)
        .bind(&calendar.color)
        .bind(&calendar.is_visible)
        .bind(Utc::now())
        .bind(id)
        .bind(&calendar.user_id)
        .execute(self.db.pool())
        .await?;

        Ok(())
    }

    /// Delete an external calendar
    pub async fn delete(&self, id: i32, user_id: &str) -> Result<()> {
        sqlx::query("DELETE FROM external_calendars WHERE id = ? AND user_id = ?")
            .bind(id)
            .bind(user_id)
            .execute(self.db.pool())
            .await?;

        Ok(())
    }

    /// Update last_fetched timestamp
    pub async fn update_last_fetched(&self, id: i32, user_id: &str) -> Result<()> {
        sqlx::query(
            "UPDATE external_calendars SET last_fetched = ? WHERE id = ? AND user_id = ?",
        )
        .bind(Utc::now())
        .bind(id)
        .bind(user_id)
        .execute(self.db.pool())
        .await?;

        Ok(())
    }

    /// Get cached ICS data for an external calendar
    pub async fn get_cached_data(&self, external_calendar_id: i32) -> Result<Option<(String, DateTime<Utc>)>> {
        let result = sqlx::query_as::<_, (String, DateTime<Utc>)>(
            "SELECT ics_data, cached_at FROM external_calendar_cache WHERE external_calendar_id = ?",
        )
        .bind(external_calendar_id)
        .fetch_optional(self.db.pool())
        .await?;

        Ok(result)
    }

    /// Update cache with new ICS data
    pub async fn update_cache(&self, external_calendar_id: i32, ics_data: &str, etag: Option<&str>) -> Result<()> {
        sqlx::query(
            "INSERT INTO external_calendar_cache (external_calendar_id, ics_data, etag, cached_at)
             VALUES (?, ?, ?, ?)
             ON CONFLICT(external_calendar_id) DO UPDATE SET
             ics_data = excluded.ics_data,
             etag = excluded.etag,
             cached_at = excluded.cached_at",
        )
        .bind(external_calendar_id)
        .bind(ics_data)
        .bind(etag)
        .bind(Utc::now())
        .execute(self.db.pool())
        .await?;

        Ok(())
    }

    /// Check if cache is stale (older than max_age_minutes)
    pub async fn is_cache_stale(&self, external_calendar_id: i32, max_age_minutes: i64) -> Result<bool> {
        let cutoff_time = Utc::now() - Duration::minutes(max_age_minutes);
        
        let result = sqlx::query_scalar::<_, i64>(
            "SELECT COUNT(*) FROM external_calendar_cache 
             WHERE external_calendar_id = ? AND cached_at > ?",
        )
        .bind(external_calendar_id)
        .bind(cutoff_time)
        .fetch_one(self.db.pool())
        .await?;

        Ok(result == 0)
    }

    /// Clear cache for an external calendar
    pub async fn clear_cache(&self, external_calendar_id: i32) -> Result<()> {
        sqlx::query("DELETE FROM external_calendar_cache WHERE external_calendar_id = ?")
            .bind(external_calendar_id)
            .execute(self.db.pool())
            .await?;

        Ok(())
    }
}