use anyhow::Result;
use chrono::Utc;
use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgUser {
    pub username: String,
    pub password_hash: String,
    pub email: Option<String>,
    pub status: i32,
    pub home_dir: String,
    pub permissions: String,
    pub uid: i64,
    pub gid: i64,
    pub last_login: i64,
    pub created_at: i64,
    pub updated_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgGroup {
    pub name: String,
    pub description: Option<String>,
    pub created_at: i64,
    pub updated_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgUserGroupMapping {
    pub username: String,
    pub group_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PgAdmin {
    pub username: String,
    pub password_hash: String,
    pub email: Option<String>,
    pub description: Option<String>,
    pub status: i32,
    pub permissions: String,
    pub filters: Option<String>,
    pub role_id: Option<i32>,
    pub last_login: i64,
    pub created_at: i64,
    pub updated_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncResult {
    pub sync_type: String,
    pub sync_time: i64,
    pub users_synced: usize,
    pub users_failed: usize,
    pub groups_synced: usize,
    pub groups_failed: usize,
    pub mappings_synced: usize,
    pub mappings_failed: usize,
    pub admins_synced: usize,
    pub admins_failed: usize,
    pub status: String,
    pub errors: Vec<String>,
}

impl Default for SyncResult {
    fn default() -> Self {
        Self {
            sync_type: "unknown".to_string(),
            sync_time: Utc::now().timestamp(),
            users_synced: 0,
            users_failed: 0,
            groups_synced: 0,
            groups_failed: 0,
            mappings_synced: 0,
            mappings_failed: 0,
            admins_synced: 0,
            admins_failed: 0,
            status: "pending".to_string(),
            errors: Vec::new(),
        }
    }
}

impl SyncResult {
    pub fn success() -> Self {
        Self {
            status: "success".to_string(),
            ..Default::default()
        }
    }
    
    pub fn cached() -> Self {
        Self {
            status: "cached".to_string(),
            ..Default::default()
        }
    }
    
    pub fn failed(error: String) -> Self {
        Self {
            status: "failed".to_string(),
            errors: vec![error],
            ..Default::default()
        }
    }
    
    pub fn merge(&mut self, other: SyncResult) {
        self.users_synced += other.users_synced;
        self.users_failed += other.users_failed;
        self.groups_synced += other.groups_synced;
        self.groups_failed += other.groups_failed;
        self.mappings_synced += other.mappings_synced;
        self.mappings_failed += other.mappings_failed;
        self.errors.extend(other.errors);
        
        if self.users_failed > 0 || self.groups_failed > 0 || self.mappings_failed > 0 {
            if self.users_synced > 0 || self.groups_synced > 0 || self.mappings_synced > 0 {
                self.status = "partial_success".to_string();
            } else {
                self.status = "failed".to_string();
            }
        }
    }
}

#[derive(Clone)]
pub struct AuthDb {
    pub path: String,
}

impl AuthDb {
    pub fn new(path: &str) -> Result<Self> {
        if !Path::new(path).exists() {
            Self::init_db(path)?;
        }
        Ok(Self { path: path.to_string() })
    }
    
    pub fn init_db(path: &str) -> Result<()> {
        let conn = Connection::open(path)?;
        conn.execute_batch(include_str!("../data/init_auth_db.sql"))?;
        Ok(())
    }
    
    pub fn open(&self) -> Result<Connection> {
        Ok(Connection::open(&self.path)?)
    }
    
    pub fn save_user(&self, user: &PgUser) -> Result<()> {
        let conn = self.open()?;
        let now = Utc::now().timestamp();
        
        conn.execute(
            "INSERT OR REPLACE INTO sftpgo_users 
             (username, password_hash, email, status, home_dir, permissions, 
              uid, gid, last_login, created_at, updated_at, last_sync_at, sync_status)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
            params![
                user.username,
                user.password_hash,
                user.email,
                user.status,
                user.home_dir,
                user.permissions,
                user.uid,
                user.gid,
                user.last_login,
                user.created_at,
                user.updated_at,
                now,
                1, // sync_status = synced
            ]
        )?;
        
        Ok(())
    }
    
    pub fn save_group(&self, group: &PgGroup) -> Result<()> {
        let conn = self.open()?;
        let now = Utc::now().timestamp();
        
        conn.execute(
            "INSERT OR REPLACE INTO sftpgo_groups 
             (name, description, created_at, updated_at, last_sync_at)
             VALUES (?1, ?2, ?3, ?4, ?5)",
            params![
                group.name,
                group.description,
                group.created_at,
                group.updated_at,
                now,
            ]
        )?;
        
        Ok(())
    }
    
    pub fn save_mapping(&self, mapping: &PgUserGroupMapping) -> Result<()> {
        let conn = self.open()?;
        let now = Utc::now().timestamp();
        
        conn.execute(
            "INSERT OR REPLACE INTO users_groups_mapping 
             (username, group_name, created_at)
             VALUES (?1, ?2, ?3)",
            params![
                mapping.username,
                mapping.group_name,
                now,
            ]
        )?;
        
        Ok(())
    }
    
    pub fn save_sync_log(&self, result: &SyncResult) -> Result<()> {
        let conn = self.open()?;
        
        conn.execute(
            "INSERT INTO sync_log 
             (sync_type, sync_time, users_synced, users_failed, 
              groups_synced, groups_failed, mappings_synced, 
              mappings_failed, admins_synced, admins_failed,
              status, error_message)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
            params![
                result.sync_type,
                result.sync_time,
                result.users_synced,
                result.users_failed,
                result.groups_synced,
                result.groups_failed,
                result.mappings_synced,
                result.mappings_failed,
                result.admins_synced,
                result.admins_failed,
                result.status,
                result.errors.join(";"),
            ]
        )?;
        
        log::info!(
            "Sync log saved: users={}, groups={}, mappings={}, admins={}, status={}",
            result.users_synced,
            result.groups_synced,
            result.mappings_synced,
            result.admins_synced,
            result.status
        );
        
        Ok(())
    }
    
    pub fn save_admin(&self, admin: &PgAdmin) -> Result<()> {
        let conn = self.open()?;
        
        conn.execute(
            "INSERT OR REPLACE INTO sftpgo_admins 
             (username, password_hash, email, description, status, 
              permissions, filters, role_id, last_login, 
              created_at, updated_at, last_sync_at)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
            params![
                admin.username,
                admin.password_hash,
                admin.email,
                admin.description,
                admin.status,
                admin.permissions,
                admin.filters,
                admin.role_id,
                admin.last_login,
                admin.created_at,
                admin.updated_at,
                Utc::now().timestamp(),
            ],
        )?;
        
        Ok(())
    }
    
    pub fn get_admin(&self, username: &str) -> Result<Option<PgAdmin>> {
        let conn = self.open()?;
        
        let result = conn.query_row(
            "SELECT username, password_hash, email, description, status,
                    permissions, filters, role_id, last_login, 
                    created_at, updated_at
             FROM sftpgo_admins WHERE username = ?1 AND status = 1",
            params![username],
            |row| Ok(PgAdmin {
                username: row.get(0)?,
                password_hash: row.get(1)?,
                email: row.get(2)?,
                description: row.get(3)?,
                status: row.get(4)?,
                permissions: row.get(5)?,
                filters: row.get(6)?,
                role_id: row.get(7)?,
                last_login: row.get(8)?,
                created_at: row.get(9)?,
                updated_at: row.get(10)?,
            }),
        );
        
        match result {
            Ok(admin) => Ok(Some(admin)),
            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
            Err(e) => Err(e.into()),
        }
    }
    
    pub fn sync_admins(&self, admins: Vec<PgAdmin>) -> Result<usize> {
        let mut synced = 0;
        
        for admin in admins {
            match self.save_admin(&admin) {
                Ok(_) => synced += 1,
                Err(e) => log::warn!("Failed to sync admin {}: {}", admin.username, e),
            }
        }
        
        Ok(synced)
    }
    
    pub fn get_user(&self, username: &str) -> Result<Option<PgUser>> {
        let conn = self.open()?;
        
        let result = conn.query_row(
            "SELECT username, password_hash, email, status, home_dir, permissions,
                    uid, gid, last_login, created_at, updated_at
             FROM sftpgo_users WHERE username = ?1 AND status = 1",
            params![username],
            |row| Ok(PgUser {
                username: row.get(0)?,
                password_hash: row.get(1)?,
                email: row.get(2)?,
                status: row.get(3)?,
                home_dir: row.get(4)?,
                permissions: row.get(5)?,
                uid: row.get(6)?,
                gid: row.get(7)?,
                last_login: row.get(8)?,
                created_at: row.get(9)?,
                updated_at: row.get(10)?,
            })
        );
        
        match result {
            Ok(user) => Ok(Some(user)),
            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
            Err(e) => Err(e.into()),
        }
    }
    
    pub fn get_user_groups(&self, username: &str) -> Result<Vec<String>> {
        let conn = self.open()?;
        
        let groups: Vec<String> = conn
            .prepare(
                "SELECT group_name FROM users_groups_mapping WHERE username = ?1"
            )?
            .query_map(params![username], |row| row.get(0))?
            .collect::<Result<Vec<_>, _>>()?;
        
        Ok(groups)
    }
}