package services

import (
	"sync"

	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog/log"
	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/models"
)

// Audit event type constants
const (
	AuditEventLogin           = "auth.login"
	AuditEventLoginFailed     = "auth.login_failed"
	AuditEventRegister        = "auth.register"
	AuditEventLogout          = "auth.logout"
	AuditEventPasswordReset   = "auth.password_reset"
	AuditEventPasswordChanged = "auth.password_changed"
	AuditEventAccountDeleted  = "auth.account_deleted"
)

// AuditService handles audit logging for security-relevant events.
// It writes audit log entries asynchronously via a buffered channel to avoid
// blocking request handlers.
type AuditService struct {
	db       *gorm.DB
	logChan  chan *models.AuditLog
	done     chan struct{}
	stopOnce sync.Once
}

// NewAuditService creates a new audit service with a buffered channel for async writes.
// Call Stop() when shutting down to flush remaining entries.
func NewAuditService(db *gorm.DB) *AuditService {
	s := &AuditService{
		db:      db,
		logChan: make(chan *models.AuditLog, 256),
		done:    make(chan struct{}),
	}
	go s.processLogs()
	return s
}

// processLogs drains the log channel and writes entries to the database.
func (s *AuditService) processLogs() {
	defer close(s.done)
	for entry := range s.logChan {
		if err := s.db.Create(entry).Error; err != nil {
			log.Error().Err(err).
				Str("event_type", entry.EventType).
				Msg("Failed to write audit log entry")
		}
	}
}

// Stop closes the log channel and waits for all pending entries to be written.
// It is safe to call Stop multiple times.
func (s *AuditService) Stop() {
	s.stopOnce.Do(func() {
		close(s.logChan)
	})
	<-s.done
}

// LogEvent records an audit event. It extracts the client IP and User-Agent from
// the Echo context and sends the entry to the background writer. If the channel
// is full the entry is dropped and an error is logged (non-blocking).
func (s *AuditService) LogEvent(c echo.Context, userID *uint, eventType string, details map[string]interface{}) {
	var ip, ua string
	if c != nil {
		ip = c.RealIP()
		ua = c.Request().Header.Get("User-Agent")
	}

	entry := &models.AuditLog{
		UserID:    userID,
		EventType: eventType,
		IPAddress: ip,
		UserAgent: ua,
		Details:   models.JSONMap(details),
	}

	select {
	case s.logChan <- entry:
		// sent
	default:
		log.Warn().
			Str("event_type", eventType).
			Msg("Audit log channel full, dropping entry")
	}
}
