// 本包承载 user-service 内 Host/Agency/BD 的 MySQL 持久化。
// 父级 mysql.Repository 负责连接池；本包负责领域行锁、命令幂等记录和事件箱写入。
package host

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

	mysqldriver "github.com/go-sql-driver/mysql"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	hostdomain "hyapp/services/user-service/internal/domain/host"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

// 存储对象是 Host 领域在 user-service MySQL 上的持久化实现。
type Repository struct {
	// 数据库连接由父级 user-service MySQL 存储对象持有，本包只复用连接池。
	db *sql.DB
}

// 基于 user-service 共享连接池创建 Host 领域存储对象。
func New(db *sql.DB) *Repository {
	return &Repository{db: db}
}

const (
	// 列清单集中维护，保证查询函数和结果回放使用同一组投影字段。
	// 可空外键在存储边界统一转成 0，让领域结构体保持普通值类型。
	hostProfileColumns = `
		user_id, status, region_id, COALESCE(current_agency_id, 0),
		COALESCE(current_membership_id, 0), source, first_became_host_at_ms,
		created_at_ms, updated_at_ms`
	agencyColumns = `
		agency_id, owner_user_id, region_id, parent_bd_user_id, name, status,
		join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms`
	bdProfileColumns = `
		user_id, role, region_id, COALESCE(parent_leader_user_id, 0), status,
		created_by_user_id, created_at_ms, updated_at_ms`
	coinSellerProfileColumns = `
		user_id, status, merchant_asset_type,
		created_by_user_id, created_at_ms, updated_at_ms`
	agencyMembershipColumns = `
		membership_id, agency_id, host_user_id, region_id, membership_type, status,
		joined_at_ms, COALESCE(ended_at_ms, 0), COALESCE(ended_by_user_id, 0),
		ended_reason, created_at_ms, updated_at_ms`
	agencyApplicationColumns = `
		application_id, command_id, applicant_user_id, agency_id, region_id, status,
		COALESCE(reviewed_by_user_id, 0), review_reason, COALESCE(reviewed_at_ms, 0),
		created_at_ms, updated_at_ms`
	roleInvitationColumns = `
		invitation_id, command_id, invitation_type, status, inviter_user_id,
		inviter_bd_user_id, target_user_id, region_id, agency_name, parent_bd_user_id,
		COALESCE(parent_leader_user_id, 0), COALESCE(processed_by_user_id, 0),
		process_reason, COALESCE(processed_at_ms, 0), created_at_ms, updated_at_ms`
)

type sqlQueryer interface {
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

type rowScanner interface {
	Scan(dest ...any) error
}

// nullableRegionID 把领域层“0 表示缺失”的约定转换为 SQL NULL。
// 这个函数也用于可选用户 ID、成员关系 ID，不只用于区域 ID。
func nullableRegionID(value int64) any {
	if value <= 0 {
		return nil
	}
	return value
}

func (r *Repository) userRegion(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (int64, error) {
	// Host 领域不拥有 users 表，但所有角色流转都依赖用户当前有效区域。
	// 调用方需要串行化用户身份变化时传入 FOR UPDATE 来锁住 users 行。
	query := "SELECT COALESCE(region_id, 0), status FROM users WHERE app_code = ? AND user_id = ?"
	if strings.TrimSpace(lockClause) != "" {
		query += " " + lockClause
	}
	var regionID int64
	var status string
	err := q.QueryRowContext(ctx, query, appcode.FromContext(ctx), userID).Scan(&regionID, &status)
	if err == sql.ErrNoRows {
		return 0, xerr.New(xerr.NotFound, "user not found")
	}
	if err != nil {
		return 0, err
	}
	if status != "active" {
		return 0, xerr.New(xerr.UserDisabled, "user is not active")
	}
	return regionID, nil
}

func requireActiveRegion(ctx context.Context, q sqlQueryer, regionID int64) error {
	if regionID <= 0 {
		return xerr.New(xerr.InvalidArgument, "region_id is required")
	}
	var status string
	err := q.QueryRowContext(ctx, `
		SELECT status
		FROM regions
		WHERE app_code = ? AND region_id = ?
	`, appcode.FromContext(ctx), regionID).Scan(&status)
	if err == sql.ErrNoRows {
		return xerr.New(xerr.RegionNotFound, "region not found")
	}
	if err != nil {
		return err
	}
	if status != userdomain.RegionStatusActive {
		return xerr.New(xerr.RegionDisabled, "region is disabled")
	}
	return nil
}

func appScopedClause(ctx context.Context, clause string, args ...any) (string, []any) {
	trimmed := strings.TrimSpace(clause)
	if strings.HasPrefix(strings.ToUpper(trimmed), "WHERE ") {
		return "WHERE app_code = ? AND " + strings.TrimSpace(trimmed[len("WHERE "):]), append([]any{appcode.FromContext(ctx)}, args...)
	}
	if trimmed == "" {
		return "WHERE app_code = ?", []any{appcode.FromContext(ctx)}
	}
	return clause, args
}

func mapHostDuplicateError(err error) error {
	// 唯一键是显式行锁之后的最终并发兜底。
	// 这里统一转成领域冲突，避免业务层/传输层感知 MySQL 错误码。
	var mysqlErr *mysqldriver.MySQLError
	if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
		return xerr.New(xerr.Conflict, "host command or relationship already exists")
	}
	return err
}

func requireCommandResult(result hostdomain.CommandResult, commandType string, resultType string) error {
	if result.CommandType != commandType || result.ResultType != resultType {
		// 同一个 command_id 不能跨动作复用，否则调用方无法判断重试语义。
		return xerr.New(xerr.Conflict, "command_id was used by another host command")
	}
	return nil
}
