use aes::cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7};
use base64::Engine;
use cbc::Decryptor;
use chrono::Utc;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::LazyLock;
use std::time::Instant;
use tokio::sync::Mutex;
use uuid::Uuid;

use crate::auth_state::AuthState;
use crate::dto::{LoginResp, UserResp, WechatLoginResp};
use crate::entity::wechat_user;
use crate::error::{AuthError, AuthResult};
use crate::service::auth_service::JwtConfig;
use crate::service::token_service::TokenService;
use erp_core::sanitize::sanitize_string;

type Aes128CbcDec = Decryptor<aes::Aes128>;

/// 内存降级缓存（Redis 不可用时使用）
struct SessionEntry {
    session_key: String,
    created_at: Instant,
}

static MEMORY_FALLBACK: LazyLock<Mutex<HashMap<String, SessionEntry>>> =
    LazyLock::new(|| Mutex::new(HashMap::new()));

const SESSION_TTL_SECS: u64 = 300;

/// Redis key 前缀
const REDIS_KEY_PREFIX: &str = "wechat:session:";

#[derive(Debug, Deserialize)]
struct WechatSessionResp {
    openid: Option<String>,
    session_key: Option<String>,
    #[allow(dead_code)]
    unionid: Option<String>,
    errcode: Option<i32>,
    errmsg: Option<String>,
}

pub struct WechatService;

impl WechatService {
    pub async fn login(
        state: &AuthState,
        tenant_id: Uuid,
        code: &str,
    ) -> AuthResult<WechatLoginResp> {
        tracing::info!(
            appid = %state.wechat_appid,
            code = %code,
            "fetch_session 开始"
        );
        let session = fetch_session(
            &state.wechat_appid,
            &state.wechat_secret,
            code,
            state.wechat_dev_mode,
        )
        .await?;

        let openid = session
            .openid
            .clone()
            .ok_or_else(|| AuthError::Validation("微信登录失败：未获取到 openid".to_string()))?;

        // 缓存 session_key（Redis 优先，内存降级）
        if let Some(sk) = &session.session_key
            && let Err(e) = Self::store_session_key_redis(&state.redis, &openid, sk).await
        {
            tracing::warn!(openid = %openid, error = %e, "Redis session_key 存储失败，降级内存");
            let mut cache = MEMORY_FALLBACK.lock().await;
            cache.insert(
                openid.clone(),
                SessionEntry {
                    session_key: sk.clone(),
                    created_at: Instant::now(),
                },
            );
        }

        let existing = wechat_user::Entity::find()
            .filter(wechat_user::Column::Openid.eq(&openid))
            .filter(wechat_user::Column::TenantId.eq(tenant_id))
            .filter(wechat_user::Column::DeletedAt.is_null())
            .one(&state.db)
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        if let Some(wu) = existing {
            let token = build_login_resp(
                &state.db,
                wu.user_id,
                tenant_id,
                &JwtConfig {
                    secret: &state.jwt_secret,
                    access_ttl_secs: state.access_ttl_secs,
                    refresh_ttl_secs: state.refresh_ttl_secs,
                },
            )
            .await?;
            Ok(WechatLoginResp {
                bound: true,
                openid,
                token: Some(token),
            })
        } else {
            Ok(WechatLoginResp {
                bound: false,
                openid,
                token: None,
            })
        }
    }

    pub async fn bind_phone(
        state: &AuthState,
        tenant_id: Uuid,
        openid: &str,
        encrypted_data: &str,
        iv: &str,
    ) -> AuthResult<LoginResp> {
        // Dev 模式：mock session_key 无法解密真实微信加密数据，直接使用 mock 手机号
        let phone = if state.wechat_dev_mode {
            let hash = openid
                .bytes()
                .fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
            let suffix = hash % 10000;
            tracing::warn!(%openid, mock_phone = format!("1380000{suffix:04}"), "开发模式：跳过手机号解密，使用 mock 手机号");
            format!("1380000{suffix:04}")
        } else {
            let session_key = Self::get_session_key(&state.redis, openid).await?;
            decrypt_phone_number(&session_key, encrypted_data, iv)?
        };

        let existing = wechat_user::Entity::find()
            .filter(wechat_user::Column::Openid.eq(openid))
            .filter(wechat_user::Column::TenantId.eq(tenant_id))
            .filter(wechat_user::Column::DeletedAt.is_null())
            .one(&state.db)
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        if existing.is_some() {
            return Err(AuthError::Validation("该微信已绑定账号".to_string()));
        }

        let user_id =
            Self::find_or_create_user_by_phone(&state.db, tenant_id, &phone, &state.crypto).await?;

        let now = Utc::now();
        let wu = wechat_user::ActiveModel {
            id: Set(Uuid::now_v7()),
            tenant_id: Set(tenant_id),
            openid: Set(openid.to_string()),
            union_id: Set(None),
            user_id: Set(user_id),
            phone: Set(Some(phone.clone())),
            created_at: Set(now),
            updated_at: Set(now),
            created_by: Set(Some(user_id)),
            updated_by: Set(Some(user_id)),
            deleted_at: Set(None),
            version: Set(1),
        };
        wu.insert(&state.db)
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        build_login_resp(
            &state.db,
            user_id,
            tenant_id,
            &JwtConfig {
                secret: &state.jwt_secret,
                access_ttl_secs: state.access_ttl_secs,
                refresh_ttl_secs: state.refresh_ttl_secs,
            },
        )
        .await
    }

    async fn find_or_create_user_by_phone(
        db: &sea_orm::DatabaseConnection,
        tenant_id: Uuid,
        phone: &str,
        crypto: &erp_core::crypto::PiiCrypto,
    ) -> AuthResult<Uuid> {
        use crate::entity::user;

        let existing = user::Entity::find()
            .filter(user::Column::Phone.eq(phone))
            .filter(user::Column::TenantId.eq(tenant_id))
            .filter(user::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        if let Some(u) = existing {
            return Ok(u.id);
        }

        let now = Utc::now();
        let user_id = Uuid::now_v7();
        let suffix = &phone[phone.len().saturating_sub(4)..];

        let new_user = user::ActiveModel {
            id: Set(user_id),
            tenant_id: Set(tenant_id),
            username: Set(format!("wx_{}", suffix)),
            display_name: Set(Some(sanitize_string(&format!("微信用户{}", suffix)))),
            phone: Set(Some(phone.to_string())),
            email: Set(None),
            avatar_url: Set(None),
            status: Set("active".to_string()),
            last_login_at: Set(None),
            created_at: Set(now),
            updated_at: Set(now),
            created_by: Set(user_id),
            updated_by: Set(user_id),
            deleted_at: Set(None),
            version: Set(1),
        };
        new_user
            .insert(db)
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        // 自动分配 patient 角色
        Self::assign_patient_role(db, tenant_id, user_id).await?;

        // 自动创建或关联 patient 记录
        Self::ensure_patient_record(db, tenant_id, user_id, phone, crypto).await?;

        Ok(user_id)
    }

    async fn assign_patient_role(
        db: &sea_orm::DatabaseConnection,
        tenant_id: Uuid,
        user_id: Uuid,
    ) -> AuthResult<()> {
        use crate::entity::role;
        use crate::entity::user_role;

        let patient_role = role::Entity::find()
            .filter(role::Column::Code.eq("patient"))
            .filter(role::Column::TenantId.eq(tenant_id))
            .filter(role::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        if let Some(r) = patient_role {
            let now = Utc::now();
            let ur = user_role::ActiveModel {
                user_id: Set(user_id),
                role_id: Set(r.id),
                tenant_id: Set(tenant_id),
                created_at: Set(now),
                updated_at: Set(now),
                created_by: Set(user_id),
                updated_by: Set(user_id),
                deleted_at: Set(None),
                version: Set(1),
            };
            ur.insert(db)
                .await
                .map_err(|e| AuthError::DbError(e.to_string()))?;
            tracing::info!(%user_id, role_id = %r.id, "已为新用户分配 patient 角色");
        } else {
            tracing::warn!(%tenant_id, "patient 角色不存在，跳过角色分配");
        }

        Ok(())
    }

    /// 自动创建或关联 patient 记录。
    ///
    /// 1. 如果已有 user_id 关联的 patient → 跳过
    /// 2. 如果手机号盲索引匹配到未绑定的已有患者 → 合并（关联 user_id）
    /// 3. 否则 → 创建新的 patient 记录
    async fn ensure_patient_record(
        db: &sea_orm::DatabaseConnection,
        tenant_id: Uuid,
        user_id: Uuid,
        phone: &str,
        crypto: &erp_core::crypto::PiiCrypto,
    ) -> AuthResult<()> {
        use sea_orm::{ConnectionTrait, Statement};

        // 使用 raw SQL 避免跨 crate 依赖 erp-health 的 entity
        let result: Option<sea_orm::QueryResult> = db
            .query_one(Statement::from_sql_and_values(
                sea_orm::DatabaseBackend::Postgres,
                "SELECT id FROM patient WHERE user_id = $1 AND tenant_id = $2 AND deleted_at IS NULL LIMIT 1",
                [user_id.into(), tenant_id.into()],
            ))
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        if result.is_some() {
            tracing::debug!(%user_id, "patient 记录已存在，跳过创建");
            return Ok(());
        }

        // 智能合并：用手机号盲索引查找未绑定的已有患者（管理员/护士建档）
        let phone_hash = erp_core::crypto::hmac_hash(crypto.hmac_key(), phone);
        let blind_match: Option<sea_orm::QueryResult> = db
            .query_one(Statement::from_sql_and_values(
                sea_orm::DatabaseBackend::Postgres,
                r#"SELECT bi.entity_id AS patient_id
                   FROM blind_index bi
                   JOIN patient p ON p.id = bi.entity_id AND p.tenant_id = $2 AND p.deleted_at IS NULL
                   WHERE bi.entity_type = 'patient'
                     AND bi.field_name = 'emergency_contact_phone'
                     AND bi.blind_hash = $1
                     AND bi.tenant_id = $2
                     AND p.user_id IS NULL
                   LIMIT 1"#,
                [phone_hash.as_str().into(), tenant_id.into()],
            ))
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;

        if let Some(row) = blind_match {
            let patient_id: Uuid = row
                .try_get("", "patient_id")
                .map_err(|e| AuthError::DbError(format!("blind_index parse: {}", e)))?;
            db.execute(Statement::from_sql_and_values(
                sea_orm::DatabaseBackend::Postgres,
                "UPDATE patient SET user_id = $1, updated_at = NOW(), updated_by = $1 WHERE id = $2 AND user_id IS NULL",
                [user_id.into(), patient_id.into()],
            ))
            .await
            .map_err(|e| AuthError::DbError(e.to_string()))?;
            tracing::info!(%user_id, %patient_id, "手机号盲索引合并 patient");
            return Ok(());
        }

        let suffix = &phone[phone.len().saturating_sub(4)..];
        let patient_id = Uuid::now_v7();
        let now = Utc::now();

        db.execute(Statement::from_sql_and_values(
            sea_orm::DatabaseBackend::Postgres,
            r#"INSERT INTO patient (id, tenant_id, user_id, name, gender, status, verification_status, source, created_at, updated_at, created_by, updated_by, deleted_at, version)
               VALUES ($1, $2, $3, $4, NULL, 'active', 'pending', 'wechat_miniprogram', $5, $5, $3, $3, NULL, 1)
               ON CONFLICT DO NOTHING"#,
            [
                patient_id.into(),
                tenant_id.into(),
                user_id.into(),
                sanitize_string(&format!("微信用户{}", suffix)).into(),
                now.into(),
            ],
        ))
        .await
        .map_err(|e| AuthError::DbError(e.to_string()))?;

        tracing::info!(%user_id, %patient_id, "已自动创建 patient 记录");
        Ok(())
    }

    async fn store_session_key_redis(
        redis: &Option<redis::Client>,
        openid: &str,
        session_key: &str,
    ) -> AuthResult<()> {
        let client = redis
            .as_ref()
            .ok_or_else(|| AuthError::DbError("Redis 未配置".into()))?;
        let mut conn = client
            .get_multiplexed_async_connection()
            .await
            .map_err(|e| AuthError::DbError(format!("Redis 连接失败: {e}")))?;
        let key = format!("{}{}", REDIS_KEY_PREFIX, openid);
        redis::cmd("SET")
            .arg(&key)
            .arg(session_key)
            .arg("EX")
            .arg(SESSION_TTL_SECS)
            .query_async::<String>(&mut conn)
            .await
            .map_err(|e| AuthError::DbError(format!("Redis SET 失败: {e}")))?;
        Ok(())
    }

    async fn get_session_key(redis: &Option<redis::Client>, openid: &str) -> AuthResult<String> {
        // 1. 尝试 Redis
        if let Some(client) = redis
            && let Ok(mut conn) = client.get_multiplexed_async_connection().await
        {
            let key = format!("{}{}", REDIS_KEY_PREFIX, openid);
            let result: Option<String> = redis::cmd("GETDEL")
                .arg(&key)
                .query_async::<Option<String>>(&mut conn)
                .await
                .unwrap_or(None);
            if let Some(sk) = result {
                return Ok(sk);
            }
        }

        // 2. 降级到内存
        let mut cache = MEMORY_FALLBACK.lock().await;
        if let Some(entry) = cache.get(openid) {
            if entry.created_at.elapsed().as_secs() < SESSION_TTL_SECS {
                let sk = entry.session_key.clone();
                cache.remove(openid);
                return Ok(sk);
            }
            cache.remove(openid);
        }

        Err(AuthError::Validation(
            "未找到 session_key，请重新登录".to_string(),
        ))
    }
}

/// AES-128-CBC 解密微信手机号
fn decrypt_phone_number(session_key: &str, encrypted_data: &str, iv: &str) -> AuthResult<String> {
    let engine = base64::engine::general_purpose::STANDARD;

    let key_bytes = engine
        .decode(session_key)
        .map_err(|e| AuthError::Validation(format!("session_key base64 解码失败: {}", e)))?;
    let iv_bytes = engine
        .decode(iv)
        .map_err(|e| AuthError::Validation(format!("iv base64 解码失败: {}", e)))?;
    let ciphertext = engine
        .decode(encrypted_data)
        .map_err(|e| AuthError::Validation(format!("encrypted_data base64 解码失败: {}", e)))?;

    if key_bytes.len() != 16 {
        return Err(AuthError::Validation("session_key 长度不正确".to_string()));
    }
    if iv_bytes.len() != 16 {
        return Err(AuthError::Validation("iv 长度不正确".to_string()));
    }

    let decryptor = Aes128CbcDec::new_from_slices(&key_bytes, &iv_bytes)
        .map_err(|e| AuthError::Validation(format!("AES 初始化失败: {}", e)))?;

    let mut buf = ciphertext;
    let decrypted = decryptor
        .decrypt_padded_mut::<Pkcs7>(&mut buf)
        .map_err(|e| AuthError::Validation(format!("AES 解密失败: {}", e)))?;

    let plaintext = String::from_utf8(decrypted.to_vec())
        .map_err(|_| AuthError::Validation("解密结果非 UTF-8".to_string()))?;

    // 微信返回的 JSON 包含 watermark 等字段，提取 phone_number
    let info: serde_json::Value = serde_json::from_str(&plaintext)
        .map_err(|e| AuthError::Validation(format!("解密结果 JSON 解析失败: {}", e)))?;

    info.get("phoneNumber")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .ok_or_else(|| AuthError::Validation("解密结果中无 phoneNumber".to_string()))
}

async fn build_login_resp(
    db: &sea_orm::DatabaseConnection,
    user_id: Uuid,
    tenant_id: Uuid,
    jwt: &JwtConfig<'_>,
) -> AuthResult<LoginResp> {
    use crate::entity::user;
    use crate::service::auth_service::AuthService;

    let user_model = user::Entity::find_by_id(user_id)
        .one(db)
        .await
        .map_err(|e| AuthError::DbError(e.to_string()))?
        .ok_or_else(|| AuthError::Validation("用户不存在".to_string()))?;

    let roles = TokenService::get_user_roles(user_id, tenant_id, db).await?;
    let permissions = TokenService::get_user_permissions(user_id, tenant_id, db).await?;

    let access_token = TokenService::sign_access_token(
        user_id,
        tenant_id,
        roles.clone(),
        permissions,
        jwt.secret,
        jwt.access_ttl_secs,
    )?;
    let (refresh_token, _) =
        TokenService::sign_refresh_token(user_id, tenant_id, db, jwt.secret, jwt.refresh_ttl_secs)
            .await?;

    let role_resps = AuthService::get_user_role_resps(user_id, tenant_id, db).await?;

    Ok(LoginResp {
        access_token,
        refresh_token,
        expires_in: jwt.access_ttl_secs as u64,
        user: UserResp {
            id: user_model.id,
            username: user_model.username,
            email: user_model.email,
            phone: user_model.phone,
            display_name: user_model.display_name,
            avatar_url: user_model.avatar_url,
            status: user_model.status,
            roles: role_resps,
            version: user_model.version,
        },
    })
}

async fn fetch_session(
    appid: &str,
    secret: &str,
    code: &str,
    dev_mode: bool,
) -> AuthResult<WechatSessionResp> {
    // 开发模式降级：跳过 jscode2session，为 DevTools 模拟器生成确定性 mock openid
    if dev_mode {
        let mock_openid = format!("dev_mock_{}", &code[..8.min(code.len())]);
        tracing::warn!(%mock_openid, "开发模式：使用 mock openid（跳过 jscode2session）");
        return Ok(WechatSessionResp {
            openid: Some(mock_openid),
            session_key: Some("dev_mock_session_key".to_string()),
            unionid: None,
            errcode: None,
            errmsg: None,
        });
    }

    let client = reqwest::Client::new();
    let resp = client
        .get("https://api.weixin.qq.com/sns/jscode2session")
        .query(&[
            ("appid", appid),
            ("secret", secret),
            ("js_code", code),
            ("grant_type", "authorization_code"),
        ])
        .send()
        .await
        .map_err(|e| AuthError::Validation(format!("微信 API 请求失败: {}", e)))?;

    let session: WechatSessionResp = resp
        .json()
        .await
        .map_err(|e| AuthError::Validation(format!("微信 API 响应解析失败: {}", e)))?;

    if let Some(errcode) = session.errcode
        && errcode != 0
    {
        let msg = session.errmsg.clone().unwrap_or_default();
        tracing::error!(errcode, errmsg = %msg, "微信 jscode2session 返回错误");
        return Err(AuthError::Validation(format!(
            "微信登录失败 ({}): {}",
            errcode, msg
        )));
    }

    tracing::info!(
        has_openid = session.openid.is_some(),
        has_session_key = session.session_key.is_some(),
        "微信 jscode2session 成功"
    );

    Ok(session)
}
