// Package user 内的 repository.go 是 sqlc 生成的查询包（usersqlc）之上的薄
// 包装层。它的唯一职责是把 "驱动层类型"（pgtype.UUID / pgtype.Timestamptz /
// int32 等）和 "领域类型"（uuid.UUID / time.Time / int / *time.Time）做双向
// 转换，并把底层的 pgx.ErrNoRows 翻译为 errs.AppError，使 service 层可以面
// 向干净的领域接口编程，无需直接依赖驱动细节。
//
// 设计取舍：
//   - 之所以保留独立的 Repository interface，是为了给后续的 service 层提供
//     可替换的 mock 注入点（E6 单测无需起 Postgres）。
//   - 所有 :one 查询都集中在此处把 pgx.ErrNoRows 翻译成对应语义的
//     errs.Code（用户查找用 NotFound、会话查找用 Unauthorized），避免
//     transport 层逐处判断。
//   - pgtype 转换工具刻意保留为包内私有，仅在本文件使用；如果其它子包
//     也需要类似映射，应当各自维护或上抽到 infra/db。
package user

import (
	"context"
	"errors"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgerrcode"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
	usersqlc "github.com/yan1h/agent-coding-workflow/internal/user/sqlc"
)

// Repository 抽象 user 模块对持久层的全部依赖。所有方法都接受 context 并
// 返回领域类型，既便于 service 层 mock，也方便日后切换驱动实现。
type Repository interface {
	CreateUser(ctx context.Context, u *User) (*User, error)
	GetUserByEmail(ctx context.Context, email string) (*User, error)
	GetUserByID(ctx context.Context, id uuid.UUID) (*User, error)
	CountUsers(ctx context.Context) (int, error)
	CountAdmins(ctx context.Context) (int, error)
	ListUsers(ctx context.Context) ([]*User, error)
	ListAdmins(ctx context.Context) ([]*User, error)
	UpdateProfile(ctx context.Context, id uuid.UUID, displayName string) (*User, error)
	SetAdmin(ctx context.Context, id uuid.UUID, isAdmin bool) (*User, error)
	SetEnabled(ctx context.Context, id uuid.UUID, enabled bool) (*User, error)
	UpdatePassword(ctx context.Context, id uuid.UUID, passwordHash string) error
	DeleteUser(ctx context.Context, id uuid.UUID) error

	CreateSession(ctx context.Context, s *Session) error
	GetSessionByTokenHash(ctx context.Context, hash []byte) (*Session, error)
	TouchSession(ctx context.Context, hash []byte) error
	DeleteSessionByTokenHash(ctx context.Context, hash []byte) error
	DeleteSessionsByUserID(ctx context.Context, userID uuid.UUID) error
}

// pgRepo 是 Repository 的 Postgres 实现，内部持有一个 sqlc 生成的 Queries。
// 字段刻意保持小写避免外泄；外部只能通过 NewPostgresRepository 构造。
type pgRepo struct {
	q *usersqlc.Queries
}

// NewPostgresRepository 用现有的 pgxpool 构造 Repository 实现。pool 必须由
// 调用方负责 Close；本仓库不接管其生命周期。
func NewPostgresRepository(pool *pgxpool.Pool) Repository {
	return &pgRepo{q: usersqlc.New(pool)}
}

// toPgUUID 把领域 uuid.UUID 转为 sqlc 期望的 pgtype.UUID。
// 始终标记 Valid=true：调用方传 uuid.Nil 即视为显式写入零值。
func toPgUUID(u uuid.UUID) pgtype.UUID {
	return pgtype.UUID{Bytes: u, Valid: true}
}

// fromPgUUID 把 pgtype.UUID 转回领域 uuid.UUID。Valid=false 时返回 uuid.Nil，
// 让上层用零值判定 "无值"，无需关心驱动层的可空封装。
func fromPgUUID(p pgtype.UUID) uuid.UUID {
	if !p.Valid {
		return uuid.Nil
	}
	return uuid.UUID(p.Bytes)
}

// rowToUser 把 sqlc 生成的 User 行转为领域 User。所有时间字段直接取 .Time
// 即可：用户表的 created_at/updated_at 在 schema 上是 NOT NULL DEFAULT now()，
// 永远会 Valid。
func rowToUser(row usersqlc.User) *User {
	return &User{
		ID:           fromPgUUID(row.ID),
		Email:        row.Email,
		PasswordHash: row.PasswordHash,
		DisplayName:  row.DisplayName,
		IsAdmin:      row.IsAdmin,
		Enabled:      row.Enabled,
		CreatedAt:    row.CreatedAt.Time,
		UpdatedAt:    row.UpdatedAt.Time,
	}
}

// rowToSession 把 sqlc 生成的 UserSession 行转为领域 Session。LastSeenAt 在
// schema 上可空，因此映射为 *time.Time：未被 touch 过的会话该字段为 nil。
func rowToSession(row usersqlc.UserSession) *Session {
	var lastSeen *time.Time
	if row.LastSeenAt.Valid {
		t := row.LastSeenAt.Time
		lastSeen = &t
	}
	return &Session{
		ID:         fromPgUUID(row.ID),
		UserID:     fromPgUUID(row.UserID),
		TokenHash:  row.TokenHash,
		ExpiresAt:  row.ExpiresAt.Time,
		LastSeenAt: lastSeen,
		CreatedAt:  row.CreatedAt.Time,
	}
}

// CreateUser 写入一条新用户记录，并把 RETURNING 子句拿到的完整行映射回
// 领域 User（包含 DB 填充的 created_at/updated_at）。
func (r *pgRepo) CreateUser(ctx context.Context, u *User) (*User, error) {
	row, err := r.q.CreateUser(ctx, usersqlc.CreateUserParams{
		ID:           toPgUUID(u.ID),
		Email:        u.Email,
		PasswordHash: u.PasswordHash,
		DisplayName:  u.DisplayName,
		IsAdmin:      u.IsAdmin,
	})
	if err != nil {
		if isUniqueViolation(err) {
			return nil, errs.New(errs.CodeConflict, "邮箱已被占用")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "create user")
	}
	return rowToUser(row), nil
}

// isUniqueViolation 判断底层错误是否为 Postgres 唯一约束冲突（如 email 重复）。
func isUniqueViolation(err error) bool {
	var pgErr *pgconn.PgError
	return errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation
}

// GetUserByEmail 按邮箱精确查询用户。pgx.ErrNoRows 翻译为 NotFound，方便
// 上层（例如登录流程）分类响应；其它错误一律 Internal。
func (r *pgRepo) GetUserByEmail(ctx context.Context, email string) (*User, error) {
	row, err := r.q.GetUserByEmail(ctx, email)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errs.New(errs.CodeNotFound, "用户不存在")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "get user by email")
	}
	return rowToUser(row), nil
}

// GetUserByID 按主键查询用户。错误处理与 GetUserByEmail 对称。
func (r *pgRepo) GetUserByID(ctx context.Context, id uuid.UUID) (*User, error) {
	row, err := r.q.GetUserByID(ctx, toPgUUID(id))
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errs.New(errs.CodeNotFound, "用户不存在")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "get user by id")
	}
	return rowToUser(row), nil
}

// CountUsers 返回用户总数。sqlc 生成的签名是 int32；上层通常用 int 做条件
// 判断（例如 Bootstrap 决定是否首启），这里直接转 int 简化调用。
func (r *pgRepo) CountUsers(ctx context.Context) (int, error) {
	n, err := r.q.CountUsers(ctx)
	if err != nil {
		return 0, errs.Wrap(err, errs.CodeInternal, "count users")
	}
	return int(n), nil
}

// CreateSession 写入一条新会话。ExpiresAt 必填，因此始终标 Valid=true；
// last_seen_at 由 schema 默认 NULL，无需在此显式传。
func (r *pgRepo) CreateSession(ctx context.Context, s *Session) error {
	if err := r.q.CreateSession(ctx, usersqlc.CreateSessionParams{
		ID:        toPgUUID(s.ID),
		UserID:    toPgUUID(s.UserID),
		TokenHash: s.TokenHash,
		ExpiresAt: pgtype.Timestamptz{Time: s.ExpiresAt, Valid: true},
	}); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "create session")
	}
	return nil
}

// GetSessionByTokenHash 按 sha256 摘要反查会话。该方法主要给鉴权中间件
// 使用，因此 ErrNoRows 翻译为 Unauthorized 而非 NotFound：从语义看 "找不到
// 会话" 等价于 "未认证"，不应该把内部存在性细节透给客户端。
func (r *pgRepo) GetSessionByTokenHash(ctx context.Context, hash []byte) (*Session, error) {
	row, err := r.q.GetSessionByTokenHash(ctx, hash)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errs.New(errs.CodeUnauthorized, "session 无效")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "get session by token hash")
	}
	return rowToSession(row), nil
}

// TouchSession 把会话的 last_seen_at 更新为 now()。即便目标行不存在也不会
// 返回 ErrNoRows（:exec 不读结果集），因此无需特殊分支。
func (r *pgRepo) TouchSession(ctx context.Context, hash []byte) error {
	if err := r.q.TouchSession(ctx, hash); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "touch session")
	}
	return nil
}

// DeleteSessionByTokenHash 删除指定哈希的会话。删除不存在的行不算错误，
// 调用方可以放心当作 "登出幂等" 使用。
func (r *pgRepo) DeleteSessionByTokenHash(ctx context.Context, hash []byte) error {
	if err := r.q.DeleteSessionByTokenHash(ctx, hash); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "delete session by token hash")
	}
	return nil
}

// ListAdmins 返回所有 is_admin=true 的用户，供 jobs runner 等内部模块使用。
func (r *pgRepo) ListAdmins(ctx context.Context) ([]*User, error) {
	rows, err := r.q.ListAdmins(ctx)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "list admins")
	}
	out := make([]*User, 0, len(rows))
	for _, row := range rows {
		out = append(out, rowToUser(row))
	}
	return out, nil
}

// CountAdmins 返回启用状态的管理员数量，用于 service 层的“最后一个管理员”保护。
func (r *pgRepo) CountAdmins(ctx context.Context) (int, error) {
	n, err := r.q.CountAdmins(ctx)
	if err != nil {
		return 0, errs.Wrap(err, errs.CodeInternal, "count admins")
	}
	return int(n), nil
}

// ListUsers 返回全部用户，按创建时间升序。
func (r *pgRepo) ListUsers(ctx context.Context) ([]*User, error) {
	rows, err := r.q.ListUsers(ctx)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "list users")
	}
	out := make([]*User, 0, len(rows))
	for _, row := range rows {
		out = append(out, rowToUser(row))
	}
	return out, nil
}

// UpdateProfile 更新 display_name 并返回更新后的行。目标不存在时返回 NotFound。
func (r *pgRepo) UpdateProfile(ctx context.Context, id uuid.UUID, displayName string) (*User, error) {
	row, err := r.q.UpdateUserProfile(ctx, usersqlc.UpdateUserProfileParams{
		ID:          toPgUUID(id),
		DisplayName: displayName,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errs.New(errs.CodeNotFound, "用户不存在")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "update user profile")
	}
	return rowToUser(row), nil
}

// SetAdmin 设置 is_admin 并返回更新后的行。目标不存在时返回 NotFound。
func (r *pgRepo) SetAdmin(ctx context.Context, id uuid.UUID, isAdmin bool) (*User, error) {
	row, err := r.q.SetUserAdmin(ctx, usersqlc.SetUserAdminParams{
		ID:      toPgUUID(id),
		IsAdmin: isAdmin,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errs.New(errs.CodeNotFound, "用户不存在")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "set user admin")
	}
	return rowToUser(row), nil
}

// SetEnabled 设置 enabled 并返回更新后的行。目标不存在时返回 NotFound。
func (r *pgRepo) SetEnabled(ctx context.Context, id uuid.UUID, enabled bool) (*User, error) {
	row, err := r.q.SetUserEnabled(ctx, usersqlc.SetUserEnabledParams{
		ID:      toPgUUID(id),
		Enabled: enabled,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errs.New(errs.CodeNotFound, "用户不存在")
		}
		return nil, errs.Wrap(err, errs.CodeInternal, "set user enabled")
	}
	return rowToUser(row), nil
}

// UpdatePassword 覆写密码哈希。:exec 不读结果集，目标不存在不报错（幂等）。
func (r *pgRepo) UpdatePassword(ctx context.Context, id uuid.UUID, passwordHash string) error {
	if err := r.q.UpdateUserPassword(ctx, usersqlc.UpdateUserPasswordParams{
		ID:           toPgUUID(id),
		PasswordHash: passwordHash,
	}); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "update user password")
	}
	return nil
}

// DeleteUser 删除用户。其会话经 ON DELETE CASCADE 一并清除。
func (r *pgRepo) DeleteUser(ctx context.Context, id uuid.UUID) error {
	if err := r.q.DeleteUser(ctx, toPgUUID(id)); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "delete user")
	}
	return nil
}

// DeleteSessionsByUserID 撤销某用户的全部会话（改密/禁用时使用）。
func (r *pgRepo) DeleteSessionsByUserID(ctx context.Context, userID uuid.UUID) error {
	if err := r.q.DeleteSessionsByUserID(ctx, toPgUUID(userID)); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "delete sessions by user id")
	}
	return nil
}
