use std::error::Error;

use super::data::*;
use proto::PlayerData;
use sqlx::{query, query_as, PgPool, Row};

pub struct SqlOpPlayerData(PgPool);

impl SqlOpPlayerData {
    pub fn new(pool: PgPool) -> Self {
        Self(pool)
    }

    pub async fn select_player_uid_by_account(
        &self,
        account_uid: String,
    ) -> Result<Option<DbPlayerUidRow>, Box<dyn Error>> {
        Ok(
            query_as("SELECT * from t_user_uid where account_uid = ($1)")
                .bind(account_uid)
                .fetch_optional(&self.0)
                .await?,
        )
    }

    pub async fn insert_player_uid_by_account(
        &self,
        account_uid: String,
        platform_type: u32,
    ) -> Result<i32, Box<dyn Error>> {
        let max_uid = query("SELECT uid from t_user_uid ORDER BY uid DESC LIMIT 1")
            .fetch_optional(&self.0)
            .await?;

        let max_uid = match max_uid {
            Some(max_uid) => max_uid.try_get::<i32, _>(0)?,
            _ => 0,
        };

        let ext = serde_json::to_string(&PlayerExtJsonData {
            reg_platform: platform_type,
        })?;

        query("INSERT INTO t_user_uid VALUES ($1, $2, $3, $4)")
            .bind(max_uid + 1)
            .bind(account_uid)
            .bind(ext)
            .bind(0)
            .execute(&self.0)
            .await?;

        Ok(max_uid + 1)
    }

    pub async fn select_player_data_by_uid(
        &self,
        uid: i32,
    ) -> Result<Option<DbPlayerDataRow>, Box<dyn Error>> {
        match query_as("SELECT * from t_player_data where uid = ($1)")
            .bind(uid)
            .fetch_optional(&self.0)
            .await
        {
            Ok(data) => Ok(data),
            Err(err) => {
                tracing::error!("failed to select player data, uid: {uid}, err: {err}");
                Err(err.into())
            }
        }
    }

    pub async fn insert_player_data_by_uid(
        &self,
        uid: i32,
        data: &PlayerData,
    ) -> Result<(), Box<dyn Error>> {
        query(
            "INSERT INTO t_player_data (uid, nickname, level, exp, bin_data, is_delete, reserved_1, reserved_2) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
        )
            .bind(uid)
            .bind(data.nickname.as_str())
            .bind(data.level as i32)
            .bind(data.exp as i32)
            .bind(data.bin.clone())
            .bind(false).bind(0).bind(0)
            .execute(&self.0).await?;

        Ok(())
    }

    pub async fn update_player_data_by_uid(
        &self,
        uid: i32,
        nickname: String,
        level: i32,
        exp: i32,
        bin_data: Vec<u8>,
    ) -> Result<(), Box<dyn Error>> {
        query(
            "UPDATE t_player_data SET nickname = ($1), level = ($2), exp = ($3), bin_data = ($4) WHERE uid = ($5)",
        ).bind(nickname).bind(level).bind(exp).bind(bin_data).bind(uid).execute(&self.0).await?;

        Ok(())
    }
}
