package services

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"

	"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
	"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"
)

type AuditService struct {
	db *sqlx.DB
}

func NewAuditService(db *sqlx.DB) *AuditService {
	return &AuditService{db: db}
}

// Log records an audit entry. It extracts tenant, user, IP, and user-agent from context.
// Errors are logged but not returned — audit logging must not break business operations.
func (s *AuditService) Log(ctx context.Context, action, entityType string, entityID *uuid.UUID, oldValues, newValues any) {
	tenantID, ok := auth.TenantFromContext(ctx)
	if !ok {
		slog.Warn("audit: missing tenant_id in context", "action", action, "entity_type", entityType)
		return
	}

	var userID *uuid.UUID
	if uid, ok := auth.UserFromContext(ctx); ok {
		userID = &uid
	}

	var oldJSON, newJSON *json.RawMessage
	if oldValues != nil {
		if b, err := json.Marshal(oldValues); err == nil {
			raw := json.RawMessage(b)
			oldJSON = &raw
		}
	}
	if newValues != nil {
		if b, err := json.Marshal(newValues); err == nil {
			raw := json.RawMessage(b)
			newJSON = &raw
		}
	}

	ip := auth.IPFromContext(ctx)
	ua := auth.UserAgentFromContext(ctx)

	_, err := s.db.ExecContext(ctx,
		`INSERT INTO audit_log (tenant_id, user_id, action, entity_type, entity_id, old_values, new_values, ip_address, user_agent)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
		tenantID, userID, action, entityType, entityID, oldJSON, newJSON, ip, ua)
	if err != nil {
		slog.Error("audit: failed to write log entry",
			"error", err,
			"action", action,
			"entity_type", entityType,
			"entity_id", entityID,
		)
	}
}

// AuditFilter holds query parameters for listing audit log entries.
type AuditFilter struct {
	EntityType string
	EntityID   *uuid.UUID
	UserID     *uuid.UUID
	From       string // RFC3339 date
	To         string // RFC3339 date
	Page       int
	Limit      int
}

// List returns paginated audit log entries for a tenant.
func (s *AuditService) List(ctx context.Context, tenantID uuid.UUID, filter AuditFilter) ([]models.AuditLog, int, error) {
	if filter.Limit <= 0 {
		filter.Limit = 50
	}
	if filter.Limit > 200 {
		filter.Limit = 200
	}
	if filter.Page <= 0 {
		filter.Page = 1
	}
	offset := (filter.Page - 1) * filter.Limit

	where := "WHERE tenant_id = $1"
	args := []any{tenantID}
	argIdx := 2

	if filter.EntityType != "" {
		where += fmt.Sprintf(" AND entity_type = $%d", argIdx)
		args = append(args, filter.EntityType)
		argIdx++
	}
	if filter.EntityID != nil {
		where += fmt.Sprintf(" AND entity_id = $%d", argIdx)
		args = append(args, *filter.EntityID)
		argIdx++
	}
	if filter.UserID != nil {
		where += fmt.Sprintf(" AND user_id = $%d", argIdx)
		args = append(args, *filter.UserID)
		argIdx++
	}
	if filter.From != "" {
		where += fmt.Sprintf(" AND created_at >= $%d", argIdx)
		args = append(args, filter.From)
		argIdx++
	}
	if filter.To != "" {
		where += fmt.Sprintf(" AND created_at <= $%d", argIdx)
		args = append(args, filter.To)
		argIdx++
	}

	var total int
	if err := s.db.GetContext(ctx, &total, "SELECT COUNT(*) FROM audit_log "+where, args...); err != nil {
		return nil, 0, fmt.Errorf("counting audit entries: %w", err)
	}

	query := fmt.Sprintf("SELECT * FROM audit_log %s ORDER BY created_at DESC LIMIT $%d OFFSET $%d",
		where, argIdx, argIdx+1)
	args = append(args, filter.Limit, offset)

	var entries []models.AuditLog
	if err := s.db.SelectContext(ctx, &entries, query, args...); err != nil {
		return nil, 0, fmt.Errorf("listing audit entries: %w", err)
	}
	if entries == nil {
		entries = []models.AuditLog{}
	}

	return entries, total, nil
}
