package server

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	"security-center-backend/internal/model"
)

// auditRetentionTestLog mirrors the audit_logs schema with the full column set so
// that inserts via model.AuditLog work without pulling in the users relation.
type auditRetentionTestLog struct {
	ID           uint64 `gorm:"primaryKey"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
	DeletedAt    *time.Time `gorm:"index"`
	UserID       *uint64
	ActorType    string      `gorm:"size:50;not null;default:'user'"`
	ActorUserID  *uint64     `gorm:"index"`
	ActorEmail   string      `gorm:"size:320;index"`
	Action       string      `gorm:"size:150;not null"`
	ResourceType string      `gorm:"size:120;not null"`
	ResourceID   string      `gorm:"size:255"`
	Metadata     model.JSONB `gorm:"type:json"`
	BeforeState  model.JSONB `gorm:"type:json"`
	AfterState   model.JSONB `gorm:"type:json"`
	IPAddress    string      `gorm:"size:64"`
	UserAgent    string      `gorm:"size:255"`
}

func (auditRetentionTestLog) TableName() string {
	return "audit_logs"
}

func newAuditRetentionTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&auditRetentionTestLog{}))
	return db
}

func TestCleanupAuditLogsDeletesExpiredRowsInBatches(t *testing.T) {
	ctx := context.Background()
	db := newAuditRetentionTestDB(t)

	now := time.Now()
	expired := now.AddDate(0, 0, -100)
	cutoff := now.AddDate(0, 0, -90)

	const expiredCount = 2500
	const freshCount = 500
	rows := make([]auditRetentionTestLog, 0, expiredCount+freshCount)
	for i := 0; i < expiredCount; i++ {
		rows = append(rows, auditRetentionTestLog{
			CreatedAt:    expired,
			UpdatedAt:    expired,
			ActorType:    "user",
			Action:       "login",
			ResourceType: "auth",
		})
	}
	for i := 0; i < freshCount; i++ {
		rows = append(rows, auditRetentionTestLog{
			CreatedAt:    now,
			UpdatedAt:    now,
			ActorType:    "user",
			Action:       "login",
			ResourceType: "auth",
		})
	}
	require.NoError(t, db.CreateInBatches(&rows, 500).Error)

	deleted, err := cleanupAuditLogs(ctx, db, cutoff, 1000)
	require.NoError(t, err)
	require.EqualValues(t, expiredCount, deleted)

	var remaining int64
	require.NoError(t, db.Model(&auditRetentionTestLog{}).Count(&remaining).Error)
	require.EqualValues(t, freshCount, remaining)

	var expiredRemaining int64
	require.NoError(t, db.Model(&auditRetentionTestLog{}).
		Where("created_at < ?", cutoff).
		Count(&expiredRemaining).Error)
	require.Zero(t, expiredRemaining)
}

func TestCleanupAuditLogsEmptyTable(t *testing.T) {
	db := newAuditRetentionTestDB(t)
	deleted, err := cleanupAuditLogs(context.Background(), db, time.Now(), 1000)
	require.NoError(t, err)
	require.Zero(t, deleted)
}

func TestWriteRetentionCleanupAudit(t *testing.T) {
	ctx := context.Background()
	db := newAuditRetentionTestDB(t)

	require.NoError(t, writeRetentionCleanupAudit(ctx, db, 42, 90))

	var entry model.AuditLog
	require.NoError(t, db.First(&entry).Error)
	require.Equal(t, "system", entry.ActorType)
	require.Equal(t, "retention_cleanup", entry.Action)
	require.Equal(t, "audit", entry.ResourceType)
	require.EqualValues(t, 42, entry.Metadata["deleted"])
	require.EqualValues(t, 90, entry.Metadata["retention_days"])
	require.NotEmpty(t, entry.Metadata["ran_at"])
}

func TestResolveRetentionDaysFallback(t *testing.T) {
	require.Equal(t, defaultAuditRetentionDays, resolveRetentionDays(0))
	require.Equal(t, defaultAuditRetentionDays, resolveRetentionDays(-5))
	require.Equal(t, 30, resolveRetentionDays(30))
	require.Equal(t, 90, defaultAuditRetentionDays)
}
