// Package audit 提供登录、登出、关键管理动作等敏感事件的审计记录器。
// Recorder 接口与具体存储解耦，user 模块只依赖接口；NewPostgresRecorder
// 是默认实现，把每条 Entry 转成 audit_logs 行写入。
//
// IP 解析：调用方通常直接传 r.RemoteAddr（host:port 形式），Record 内部
// 用 net.SplitHostPort + netip.ParseAddr 拆解。解析失败时不阻断主流程，
// 只把 ip 列写为 NULL，避免审计写失败影响业务。
package audit

import (
	"context"
	"encoding/json"
	"net"
	"net/netip"

	"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"
)

// Recorder 是审计记录的对外契约。Record 失败时返回 errs.AppError，
// 调用方决定是否吞错（user 模块通常吞错并仅记日志）。
type Recorder interface {
	Record(ctx context.Context, e Entry) error
}

// Entry 描述一条待写入的审计事件。UserID 为 nil 表示无登录上下文（如系统
// 维护任务）；Action 为 dot.notation 字符串（"user.login" / "user.logout"
// 等），TargetType/TargetID 描述事件指向的对象。
type Entry struct {
	UserID     *uuid.UUID
	Action     string
	TargetType string
	TargetID   string
	IP         string
	Metadata   map[string]any
}

type pgRecorder struct {
	q *auditsqlc.Queries
}

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

// Record 把 Entry 落地为 audit_logs 一行。
func (r *pgRecorder) Record(ctx context.Context, e Entry) error {
	var meta []byte
	if e.Metadata != nil {
		b, err := json.Marshal(e.Metadata)
		if err != nil {
			return errs.Wrap(err, errs.CodeInternal, "marshal audit metadata")
		}
		meta = b
	}

	var ip *netip.Addr
	if e.IP != "" {
		host, _, err := net.SplitHostPort(e.IP)
		if err != nil {
			host = e.IP
		}
		if a, err := netip.ParseAddr(host); err == nil {
			ip = &a
		}
	}

	if err := r.q.InsertAuditLog(ctx, auditsqlc.InsertAuditLogParams{
		UserID:     pgUUIDFromPtr(e.UserID),
		Action:     e.Action,
		TargetType: ptr(e.TargetType),
		TargetID:   ptr(e.TargetID),
		Ip:         ip,
		Metadata:   meta,
	}); err != nil {
		return errs.Wrap(err, errs.CodeInternal, "insert audit")
	}
	return nil
}

// pgUUIDFromPtr 把可空的领域 *uuid.UUID 转为 sqlc 期望的 pgtype.UUID。
// nil 映射为 Valid=false（DB NULL），非 nil 映射为 Valid=true + Bytes。
func pgUUIDFromPtr(u *uuid.UUID) pgtype.UUID {
	if u == nil {
		return pgtype.UUID{Valid: false}
	}
	return pgtype.UUID{Bytes: *u, Valid: true}
}

// ptr 把空字符串映射为 nil，使数据库列写入 NULL；其它字符串原样取地址。
func ptr(s string) *string {
	if s == "" {
		return nil
	}
	return &s
}
