// Package service provides audit service business logic.
package service

import (
	"context"
	"fmt"
	"time"

	"git.dcentral.systems/toolz/goplt/internal/ent"
	"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
	"git.dcentral.systems/toolz/goplt/pkg/logger"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// AuditLogEntry represents an audit log entry.
type AuditLogEntry struct {
	UserID     string
	Action     string
	Resource   string
	ResourceID string
	IPAddress  string
	UserAgent  string
	Metadata   map[string]string
	Timestamp  int64
}

// AuditLogFilters contains filters for querying audit logs.
type AuditLogFilters struct {
	UserID     *string
	Action     *string
	Resource   *string
	ResourceID *string
	StartTime  *int64
	EndTime    *int64
	Limit      int
	Offset     int
}

// AuditService provides audit logging functionality.
type AuditService struct {
	client *ent.Client
	logger logger.Logger
}

// NewAuditService creates a new audit service.
func NewAuditService(client *ent.Client, log logger.Logger) *AuditService {
	return &AuditService{
		client: client,
		logger: log,
	}
}

// Record records an audit log entry.
func (s *AuditService) Record(ctx context.Context, entry *AuditLogEntry) error {
	// Convert metadata map to JSON
	metadataJSON := make(map[string]interface{})
	for k, v := range entry.Metadata {
		metadataJSON[k] = v
	}

	// Create audit log entry
	timestamp := time.Unix(entry.Timestamp, 0)
	if entry.Timestamp == 0 {
		timestamp = time.Now()
	}

	create := s.client.AuditLog.Create().
		SetID(uuid.New().String()).
		SetUserID(entry.UserID).
		SetAction(entry.Action).
		SetMetadata(metadataJSON).
		SetTimestamp(timestamp)

	if entry.Resource != "" {
		create = create.SetResource(entry.Resource)
	}
	if entry.ResourceID != "" {
		create = create.SetResourceID(entry.ResourceID)
	}
	if entry.IPAddress != "" {
		create = create.SetIPAddress(entry.IPAddress)
	}
	if entry.UserAgent != "" {
		create = create.SetUserAgent(entry.UserAgent)
	}

	auditLog, err := create.Save(ctx)

	if err != nil {
		s.logger.Error("Failed to record audit log",
			zap.Error(err),
			zap.String("user_id", entry.UserID),
			zap.String("action", entry.Action),
		)
		return fmt.Errorf("failed to record audit log: %w", err)
	}

	s.logger.Debug("Audit log recorded",
		zap.String("id", auditLog.ID),
		zap.String("user_id", entry.UserID),
		zap.String("action", entry.Action),
	)

	return nil
}

// Query queries audit logs based on filters.
func (s *AuditService) Query(ctx context.Context, filters *AuditLogFilters) ([]*AuditLogEntry, error) {
	query := s.client.AuditLog.Query()

	// Apply filters
	if filters.UserID != nil {
		query = query.Where(auditlog.UserID(*filters.UserID))
	}
	if filters.Action != nil {
		query = query.Where(auditlog.Action(*filters.Action))
	}
	if filters.Resource != nil {
		query = query.Where(auditlog.Resource(*filters.Resource))
	}
	if filters.ResourceID != nil {
		query = query.Where(auditlog.ResourceID(*filters.ResourceID))
	}
	if filters.StartTime != nil {
		query = query.Where(auditlog.TimestampGTE(time.Unix(*filters.StartTime, 0)))
	}
	if filters.EndTime != nil {
		query = query.Where(auditlog.TimestampLTE(time.Unix(*filters.EndTime, 0)))
	}

	// Apply pagination
	if filters.Limit > 0 {
		query = query.Limit(filters.Limit)
	}
	if filters.Offset > 0 {
		query = query.Offset(filters.Offset)
	}

	// Order by timestamp descending
	query = query.Order(ent.Desc(auditlog.FieldTimestamp))

	// Execute query
	auditLogs, err := query.All(ctx)
	if err != nil {
		s.logger.Error("Failed to query audit logs",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query audit logs: %w", err)
	}

	// Convert to service entries
	entries := make([]*AuditLogEntry, 0, len(auditLogs))
	for _, log := range auditLogs {
		// Convert metadata from map[string]interface{} to map[string]string
		metadata := make(map[string]string)
		if log.Metadata != nil {
			for k, v := range log.Metadata {
				if str, ok := v.(string); ok {
					metadata[k] = str
				} else {
					metadata[k] = fmt.Sprintf("%v", v)
				}
			}
		}

		entry := &AuditLogEntry{
			UserID:     log.UserID,
			Action:     log.Action,
			Resource:   log.Resource,
			ResourceID: log.ResourceID,
			IPAddress:  log.IPAddress,
			UserAgent:  log.UserAgent,
			Metadata:   metadata,
			Timestamp:  log.Timestamp.Unix(),
		}
		entries = append(entries, entry)
	}

	return entries, nil
}
