// reader.go 提供审计日志的只读查询路径（spec §11 步骤4）。
//
// audit.Recorder 是 write-only 门面；Reader 是与之对称的 read 门面，供 admin
// 审计查看器（GET /api/v1/admin/audit-logs）与保留期清理 runner 使用。pgReader
// 复用 auditsqlc 生成的 List/Count/Delete 查询，并在 pgtype <-> 领域类型间转换。
package audit

import (
	"context"
	"encoding/json"
	"time"

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

	auditsqlc "github.com/yan1h/agent-coding-workflow/internal/audit/sqlc"
	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
)

// LogEntry 是一条已落库审计记录的只读领域视图。区别于写入用的 Entry：含 ID/
// 时间戳，IP 以字符串形式返回。
type LogEntry struct {
	ID         int64
	UserID     *uuid.UUID
	Action     string
	TargetType string
	TargetID   string
	IP         string
	Metadata   map[string]any
	CreatedAt  time.Time
}

// AuditFilter 是 List/Count 的可选过滤条件。零值字段表示不过滤。Limit<=0 时由
// Reader 兜底为默认页大小；Limit 上限由 Reader 钳制。
type AuditFilter struct {
	UserID       *uuid.UUID
	Action       string // 精确匹配
	ActionPrefix string // 前缀匹配（搜索用）
	TargetType   string
	TargetID     string
	From         *time.Time
	To           *time.Time
	Limit        int
	Offset       int
}

const (
	// DefaultAuditPageSize 是未指定 Limit 时的默认页大小。
	DefaultAuditPageSize = 50
	// MaxAuditPageSize 是单页返回的硬上限，避免一次拉全表。
	MaxAuditPageSize = 500
)

// Reader 是审计读取的对外契约：分页列表 + 总数 + 保留期清理。
type Reader interface {
	List(ctx context.Context, f AuditFilter) ([]LogEntry, int, error)
	PurgeBefore(ctx context.Context, before time.Time) (int64, error)
}

type pgReader struct {
	q *auditsqlc.Queries
}

// NewPostgresReader 用现有 pgxpool 构造 Reader 实现。
func NewPostgresReader(pool *pgxpool.Pool) Reader {
	return &pgReader{q: auditsqlc.New(pool)}
}

// List 返回符合过滤条件的审计记录页 + 满足条件的总数（用于分页）。
func (r *pgReader) List(ctx context.Context, f AuditFilter) ([]LogEntry, int, error) {
	limit := f.Limit
	if limit <= 0 {
		limit = DefaultAuditPageSize
	}
	if limit > MaxAuditPageSize {
		limit = MaxAuditPageSize
	}
	offset := f.Offset
	if offset < 0 {
		offset = 0
	}

	rows, err := r.q.ListAuditLog(ctx, auditsqlc.ListAuditLogParams{
		Limit:        int32(limit),
		Offset:       int32(offset),
		UserID:       pgUUIDFromPtr(f.UserID),
		Action:       ptr(f.Action),
		ActionPrefix: ptr(f.ActionPrefix),
		TargetType:   ptr(f.TargetType),
		TargetID:     ptr(f.TargetID),
		FromTs:       tsFromPtr(f.From),
		ToTs:         tsFromPtr(f.To),
	})
	if err != nil {
		return nil, 0, errs.Wrap(err, errs.CodeInternal, "list audit logs")
	}

	total, err := r.q.CountAuditLog(ctx, auditsqlc.CountAuditLogParams{
		UserID:       pgUUIDFromPtr(f.UserID),
		Action:       ptr(f.Action),
		ActionPrefix: ptr(f.ActionPrefix),
		TargetType:   ptr(f.TargetType),
		TargetID:     ptr(f.TargetID),
		FromTs:       tsFromPtr(f.From),
		ToTs:         tsFromPtr(f.To),
	})
	if err != nil {
		return nil, 0, errs.Wrap(err, errs.CodeInternal, "count audit logs")
	}

	out := make([]LogEntry, 0, len(rows))
	for _, row := range rows {
		out = append(out, rowToEntry(row))
	}
	return out, int(total), nil
}

// PurgeBefore 删除 created_at < before 的全部审计记录，返回删除行数。保留期 runner 调用。
func (r *pgReader) PurgeBefore(ctx context.Context, before time.Time) (int64, error) {
	n, err := r.q.DeleteAuditLogsBefore(ctx, toPgTimestamptz(before))
	if err != nil {
		return 0, errs.Wrap(err, errs.CodeInternal, "purge audit logs")
	}
	return n, nil
}

func rowToEntry(row auditsqlc.AuditLog) LogEntry {
	e := LogEntry{
		ID:        row.ID,
		Action:    row.Action,
		CreatedAt: row.CreatedAt.Time,
	}
	if row.UserID.Valid {
		u := uuid.UUID(row.UserID.Bytes)
		e.UserID = &u
	}
	if row.TargetType != nil {
		e.TargetType = *row.TargetType
	}
	if row.TargetID != nil {
		e.TargetID = *row.TargetID
	}
	if row.Ip != nil {
		e.IP = row.Ip.String()
	}
	if len(row.Metadata) > 0 {
		_ = json.Unmarshal(row.Metadata, &e.Metadata)
	}
	return e
}

// tsFromPtr 把 *time.Time 转为 pgtype.Timestamptz；nil 映射为 NULL（不过滤）。
func tsFromPtr(t *time.Time) pgtype.Timestamptz {
	if t == nil {
		return pgtype.Timestamptz{Valid: false}
	}
	return pgtype.Timestamptz{Time: *t, Valid: true}
}

// toPgTimestamptz 把非空 time.Time 转为有效 Timestamptz。
func toPgTimestamptz(t time.Time) pgtype.Timestamptz {
	return pgtype.Timestamptz{Time: t, Valid: true}
}
