// Package db 提供数据库连接管理（MySQL 二连接实例，用于外部 LLM 配置中心）
package db

import (
	"context"
	"fmt"
	"time"

	"github.com/echochat/backend/config"
	"github.com/echochat/backend/pkg/logs"
	"go.uber.org/zap"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// LLMSourceDB 包装外部 LLM 配置库连接
//
// 之所以用 wrapper 而不是直接返回 *gorm.DB：
//   - 主库 (*gorm.DB) 在 Wire 中已被全局注入到所有 DAO，不能让 LLM 库被误用为业务库
//   - Enabled=false 时仍要构造一个空 wrapper，避免下游 nil 引用
//   - 下游 DAO 通过 IsEnabled() 决定是否走数据库路径或返回未配置错误
type LLMSourceDB struct {
	db      *gorm.DB
	enabled bool
}

// IsEnabled 是否真正连接了 MySQL；false 时禁止访问 DB()
func (l *LLMSourceDB) IsEnabled() bool {
	return l != nil && l.enabled && l.db != nil
}

// DB 获取底层 GORM 实例（仅 IsEnabled() == true 时返回非 nil）
// 调用方应先判 IsEnabled，再使用返回值。
func (l *LLMSourceDB) DB() *gorm.DB {
	if !l.IsEnabled() {
		return nil
	}
	return l.db
}

// NewLLMSourceDB 初始化外部 LLM 配置 MySQL 连接
//
// 行为约定：
//   - cfg.Enabled == false：跳过连接，返回 enabled=false 的空 wrapper（不报错），保证 Wire 流程不阻塞
//   - cfg.Enabled == true 但连接失败：返回 error，由 main 决定是否硬失败
//
// 之所以连接失败时硬报错而不是 graceful degrade：
//   - 用户已显式 enabled=true，预期就要用，沉默降级反而难排查
//   - 转写功能未启用时只需保持 enabled=false 即可
func NewLLMSourceDB(cfg *config.LLMSourceConfig) (*LLMSourceDB, error) {
	funcName := "db.NewLLMSourceDB"
	ctx := context.Background()

	if cfg == nil || !cfg.Enabled {
		logs.Info(ctx, funcName, "外部 LLM 配置库未启用，跳过 MySQL 连接（语音转写功能将不可用）")
		return &LLMSourceDB{enabled: false}, nil
	}

	logs.Info(ctx, funcName, "正在连接外部 LLM 配置 MySQL",
		zap.String("host", cfg.Host),
		zap.Int("port", cfg.Port),
		zap.String("dbname", cfg.DBName),
	)

	gdb, err := gorm.Open(mysql.Open(cfg.DSN()), &gorm.Config{
		Logger: &zapGormLogger{},
	})
	if err != nil {
		logs.Error(ctx, funcName, "MySQL 连接失败", zap.Error(err))
		return nil, fmt.Errorf("连接 LLM 配置 MySQL 失败: %w", err)
	}

	sqlDB, err := gdb.DB()
	if err != nil {
		return nil, fmt.Errorf("获取 MySQL 底层 sql.DB 失败: %w", err)
	}
	maxIdle := cfg.MaxIdleConns
	if maxIdle <= 0 {
		maxIdle = 5
	}
	maxOpen := cfg.MaxOpenConns
	if maxOpen <= 0 {
		maxOpen = 20
	}
	sqlDB.SetMaxIdleConns(maxIdle)
	sqlDB.SetMaxOpenConns(maxOpen)
	sqlDB.SetConnMaxLifetime(time.Hour)

	if err := sqlDB.Ping(); err != nil {
		logs.Error(ctx, funcName, "MySQL Ping 失败", zap.Error(err))
		return nil, fmt.Errorf("ping LLM 配置 MySQL 失败: %w", err)
	}

	logs.Info(ctx, funcName, "外部 LLM 配置 MySQL 连接成功")
	return &LLMSourceDB{db: gdb, enabled: true}, nil
}
