package service

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	"gorm.io/gorm"

	"oci-portal/internal/model"
)

// 系统日志保留策略与异步写入超时。
const (
	systemLogRetention   = 90 * 24 * time.Hour // 过期删除阈值
	systemLogMaxRows     = 50000               // 总量兜底上限，超出删最旧
	systemLogCleanupTick = 24 * time.Hour      // 周期清理间隔
	systemLogWriteWait   = 3 * time.Second     // 单条异步写入超时
)

// 系统日志查询分页默认值与上限。
const (
	systemLogDefaultPageSize = 20
	systemLogMaxPageSize     = 100
)

// SystemLogService 记录并查询面板自身的操作日志（区别于 OCI 云端审计事件）。
type SystemLogService struct {
	db *gorm.DB
	wg sync.WaitGroup
}

// NewSystemLogService 组装依赖；调用 StartCleanup 后开始周期清理。
func NewSystemLogService(db *gorm.DB) *SystemLogService {
	return &SystemLogService{db: db}
}

// Record 异步落一条日志，不阻塞业务请求；写入失败只记日志。
func (s *SystemLogService) Record(entry model.SystemLog) {
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		ctx, cancel := context.WithTimeout(context.Background(), systemLogWriteWait)
		defer cancel()
		if err := s.record(ctx, entry); err != nil {
			log.Printf("system log record: %v", err)
		}
	}()
}

// record 同步落库，供 Record 内部与测试直调。
func (s *SystemLogService) record(ctx context.Context, entry model.SystemLog) error {
	if entry.CreatedAt.IsZero() {
		entry.CreatedAt = time.Now()
	}
	if err := s.db.WithContext(ctx).Create(&entry).Error; err != nil {
		return fmt.Errorf("create system log: %w", err)
	}
	return nil
}

// Wait 等待在途异步写入与清理 goroutine 退出，供进程收尾与测试同步。
func (s *SystemLogService) Wait() {
	s.wg.Wait()
}

// SystemLogQuery 是日志查询参数；Keyword 对路径与用户名模糊匹配。
type SystemLogQuery struct {
	Page     int
	PageSize int
	Keyword  string
}

// normalize 补分页默认值并钳制上限。
func (q SystemLogQuery) normalize() SystemLogQuery {
	if q.Page < 1 {
		q.Page = 1
	}
	if q.PageSize < 1 {
		q.PageSize = systemLogDefaultPageSize
	}
	if q.PageSize > systemLogMaxPageSize {
		q.PageSize = systemLogMaxPageSize
	}
	return q
}

// List 按时间倒序分页查询日志，返回当前页与总数。
func (s *SystemLogService) List(ctx context.Context, q SystemLogQuery) ([]model.SystemLog, int64, error) {
	q = q.normalize()
	tx := s.db.WithContext(ctx).Model(&model.SystemLog{})
	if q.Keyword != "" {
		kw := "%" + q.Keyword + "%"
		tx = tx.Where("path LIKE ? OR username LIKE ?", kw, kw)
	}
	var total int64
	if err := tx.Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("count system logs: %w", err)
	}
	items := make([]model.SystemLog, 0, q.PageSize)
	err := tx.Order("created_at DESC, id DESC").
		Offset((q.Page - 1) * q.PageSize).Limit(q.PageSize).
		Find(&items).Error
	if err != nil {
		return nil, 0, fmt.Errorf("list system logs: %w", err)
	}
	return items, total, nil
}

// StartCleanup 启动周期清理：启动即清一次，之后每 24h 一次，随 ctx 取消退出。
func (s *SystemLogService) StartCleanup(ctx context.Context) {
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		s.cleanupOnce(ctx)
		ticker := time.NewTicker(systemLogCleanupTick)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				s.cleanupOnce(ctx)
			}
		}
	}()
}

// cleanupOnce 执行一轮清理，失败只记日志、不中断周期调度。
func (s *SystemLogService) cleanupOnce(ctx context.Context) {
	if err := s.cleanup(ctx, systemLogRetention, systemLogMaxRows); err != nil {
		log.Printf("system log cleanup: %v", err)
	}
}

// cleanup 先删过期记录，再对超量部分删最旧；阈值参数化便于测试。
func (s *SystemLogService) cleanup(ctx context.Context, retention time.Duration, maxRows int) error {
	cutoff := time.Now().Add(-retention)
	if err := s.db.WithContext(ctx).Where("created_at < ?", cutoff).Delete(&model.SystemLog{}).Error; err != nil {
		return fmt.Errorf("delete expired system logs: %w", err)
	}
	var total int64
	if err := s.db.WithContext(ctx).Model(&model.SystemLog{}).Count(&total).Error; err != nil {
		return fmt.Errorf("count system logs: %w", err)
	}
	overflow := int(total) - maxRows
	if overflow <= 0 {
		return nil
	}
	oldest := s.db.Model(&model.SystemLog{}).Select("id").
		Order("created_at ASC, id ASC").Limit(overflow)
	if err := s.db.WithContext(ctx).Where("id IN (?)", oldest).Delete(&model.SystemLog{}).Error; err != nil {
		return fmt.Errorf("trim system logs over cap: %w", err)
	}
	return nil
}
