// Package mysql 实现 room-service 的 MySQL 持久化边界。
package mysql

import (
	"context"
	"database/sql"
	"errors"
	"sort"
	"strings"
	"time"

	mysqlerr "github.com/go-sql-driver/mysql"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
)

const (
	roomOutboxTable = "room_outbox"
)

// Repository 把 room-service 的权威状态持久化到 MySQL。
type Repository struct {
	// db 是标准库连接池，所有 repository 方法都通过它访问 MySQL。
	db *sql.DB
}

// Open 建立 MySQL 连接池，并验证数据库可用。
func Open(ctx context.Context, dsn string, maxOpenConns int, maxIdleConns int) (*Repository, error) {
	// sql.Open 只校验 driver 和 DSN 形态，真实连通性由后续 PingContext 验证。
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}

	if maxOpenConns > 0 {
		// 只在配置显式给出正数时覆盖标准库默认值。
		db.SetMaxOpenConns(maxOpenConns)
	}

	if maxIdleConns > 0 {
		// 空闲连接数需要小于等于最大连接数，由配置层负责合理设置。
		db.SetMaxIdleConns(maxIdleConns)
	}

	// 长连接定期轮换，避免云数据库或网络层回收导致长期坏连接驻留。
	db.SetConnMaxLifetime(30 * time.Minute)

	if err := db.PingContext(ctx); err != nil {
		// 启动探测失败时必须关闭连接池，避免返回半可用 repository。
		_ = db.Close()
		return nil, err
	}

	return &Repository{db: db}, nil
}

// Close 关闭底层 MySQL 连接池。
func (r *Repository) Close() error {
	// App.Close 调用该方法释放所有空闲连接，并阻止后续新查询。
	return r.db.Close()
}

// Ping 验证 MySQL 连接池当前仍能在调用方 deadline 内拿到可用连接。
func (r *Repository) Ping(ctx context.Context) error {
	// ready 探针通过该方法确认 command log/snapshot/outbox 的持久化来源可用。
	return r.db.PingContext(ctx)
}

const (
	roomPresenceStatusActiveSQL  = "active"
	roomPresenceStatusLeftSQL    = "left"
	roomFollowStatusActiveSQL    = "active"
	roomFollowStatusCancelledSQL = "cancelled"
)

func escapeRoomListLike(value string) string {
	var builder strings.Builder
	for _, r := range value {
		if r == '\\' || r == '%' || r == '_' {
			builder.WriteRune('\\')
		}
		builder.WriteRune(r)
	}
	return builder.String()
}

func nullableString(value string) sql.Null[string] {
	// 空错误写 NULL，便于 SQL 查询区分没有错误和空字符串。
	return sql.Null[string]{
		V:     value,
		Valid: value != "",
	}
}

func nullableBytes(value []byte) any {
	// nil 结果和空 protobuf 必须都落 NULL；旧命令没有 V2 结果时幂等重放会保持历史 V1 snapshot 语义。
	if len(value) == 0 {
		return nil
	}
	return value
}

func normalizedRecordAppCode(ctx context.Context, value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return appcode.FromContext(ctx)
	}

	return appcode.Normalize(value)
}

func boolToInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func firstPositiveInt64(values ...int64) int64 {
	for _, value := range values {
		if value > 0 {
			return value
		}
	}
	return 0
}

func uniquePositiveInt64(values []int64) []int64 {
	seen := make(map[int64]bool, len(values))
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 || seen[value] {
			continue
		}
		seen[value] = true
		out = append(out, value)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}

func mapRoomMetaDuplicateError(err error) error {
	if err == nil {
		return nil
	}

	var mysqlErr *mysqlerr.MySQLError
	if !errors.As(err, &mysqlErr) || mysqlErr.Number != 1062 {
		return err
	}

	message := mysqlErr.Message
	if strings.Contains(message, "uk_rooms_owner_user") || strings.Contains(message, "owner_user_id") {
		return xerr.New(xerr.Conflict, "owner already has room")
	}

	return xerr.New(xerr.Conflict, "room already exists")
}
