package service

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/shankar0123/certctl/internal/domain"
	"github.com/shankar0123/certctl/internal/repository"
)

func TestRecordEvent(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	err := service.RecordEvent(ctx, "user123", domain.ActorTypeUser, "certificate_created", "certificate", "cert-001", map[string]interface{}{"common_name": "example.com"})
	if err != nil {
		t.Fatalf("RecordEvent failed: %v", err)
	}

	if len(auditRepo.Events) != 1 {
		t.Errorf("expected 1 event, got %d", len(auditRepo.Events))
	}

	event := auditRepo.Events[0]
	if event.Actor != "user123" {
		t.Errorf("expected actor user123, got %s", event.Actor)
	}
	if event.ActorType != domain.ActorTypeUser {
		t.Errorf("expected actor type User, got %s", event.ActorType)
	}
	if event.Action != "certificate_created" {
		t.Errorf("expected action certificate_created, got %s", event.Action)
	}
	if event.ResourceType != "certificate" {
		t.Errorf("expected resource type certificate, got %s", event.ResourceType)
	}
	if event.ResourceID != "cert-001" {
		t.Errorf("expected resource ID cert-001, got %s", event.ResourceID)
	}
}

func TestRecordEvent_RepoError(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events:    []*domain.AuditEvent{},
		CreateErr: errNotFound,
	}
	service := NewAuditService(auditRepo)

	err := service.RecordEvent(ctx, "user123", domain.ActorTypeUser, "test_action", "resource", "res-001", map[string]interface{}{})
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}

func TestListByResource(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	event1 := &domain.AuditEvent{
		ID:           "audit-1",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "created",
		ResourceType: "certificate",
		ResourceID:   "cert-001",
		Timestamp:    time.Now(),
	}
	event2 := &domain.AuditEvent{
		ID:           "audit-2",
		Actor:        "user2",
		ActorType:    domain.ActorTypeUser,
		Action:       "updated",
		ResourceType: "certificate",
		ResourceID:   "cert-001",
		Timestamp:    time.Now(),
	}
	event3 := &domain.AuditEvent{
		ID:           "audit-3",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "created",
		ResourceType: "certificate",
		ResourceID:   "cert-002",
		Timestamp:    time.Now(),
	}

	auditRepo.AddEvent(event1)
	auditRepo.AddEvent(event2)
	auditRepo.AddEvent(event3)

	events, err := service.ListByResource(ctx, "certificate", "cert-001")
	if err != nil {
		t.Fatalf("ListByResource failed: %v", err)
	}

	if len(events) != 2 {
		t.Errorf("expected 2 events, got %d", len(events))
	}
}

func TestListByActor(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	event1 := &domain.AuditEvent{
		ID:           "audit-1",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "created",
		ResourceType: "certificate",
		ResourceID:   "cert-001",
		Timestamp:    time.Now(),
	}
	event2 := &domain.AuditEvent{
		ID:           "audit-2",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "updated",
		ResourceType: "certificate",
		ResourceID:   "cert-002",
		Timestamp:    time.Now(),
	}
	event3 := &domain.AuditEvent{
		ID:           "audit-3",
		Actor:        "user2",
		ActorType:    domain.ActorTypeUser,
		Action:       "created",
		ResourceType: "certificate",
		ResourceID:   "cert-003",
		Timestamp:    time.Now(),
	}

	auditRepo.AddEvent(event1)
	auditRepo.AddEvent(event2)
	auditRepo.AddEvent(event3)

	events, err := service.ListByActor(ctx, "user1")
	if err != nil {
		t.Fatalf("ListByActor failed: %v", err)
	}

	if len(events) != 2 {
		t.Errorf("expected 2 events, got %d", len(events))
	}
}

func TestListByAction(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	now := time.Now()
	from := now.Add(-1 * time.Hour)
	to := now.Add(1 * time.Hour)

	event1 := &domain.AuditEvent{
		ID:           "audit-1",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "certificate_created",
		ResourceType: "certificate",
		ResourceID:   "cert-001",
		Timestamp:    now.Add(-30 * time.Minute),
	}
	event2 := &domain.AuditEvent{
		ID:           "audit-2",
		Actor:        "user2",
		ActorType:    domain.ActorTypeUser,
		Action:       "certificate_created",
		ResourceType: "certificate",
		ResourceID:   "cert-002",
		Timestamp:    now.Add(-20 * time.Minute),
	}
	event3 := &domain.AuditEvent{
		ID:           "audit-3",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "certificate_updated",
		ResourceType: "certificate",
		ResourceID:   "cert-001",
		Timestamp:    now.Add(-10 * time.Minute),
	}

	auditRepo.AddEvent(event1)
	auditRepo.AddEvent(event2)
	auditRepo.AddEvent(event3)

	events, err := service.ListByAction(ctx, "certificate_created", from, to)
	if err != nil {
		t.Fatalf("ListByAction failed: %v", err)
	}

	if len(events) != 2 {
		t.Errorf("expected 2 events, got %d", len(events))
	}

	for _, e := range events {
		if e.Action != "certificate_created" {
			t.Errorf("expected action certificate_created, got %s", e.Action)
		}
	}
}

func TestListByAction_EmptyRange(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	now := time.Now()
	from := now.Add(1 * time.Hour)
	to := now.Add(2 * time.Hour)

	event := &domain.AuditEvent{
		ID:           "audit-1",
		Actor:        "user1",
		ActorType:    domain.ActorTypeUser,
		Action:       "certificate_created",
		ResourceType: "certificate",
		ResourceID:   "cert-001",
		Timestamp:    now.Add(-30 * time.Minute),
	}
	auditRepo.AddEvent(event)

	events, err := service.ListByAction(ctx, "certificate_created", from, to)
	if err != nil {
		t.Fatalf("ListByAction failed: %v", err)
	}

	if len(events) != 0 {
		t.Errorf("expected 0 events, got %d", len(events))
	}
}

func TestRecordEvent_ComplexDetails(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	details := map[string]interface{}{
		"common_name": "example.com",
		"sans":        []string{"www.example.com", "api.example.com"},
		"issuer_id":   "iss-123",
		"count":       5,
	}

	err := service.RecordEvent(ctx, "user1", domain.ActorTypeUser, "certificate_created", "certificate", "cert-001", details)
	if err != nil {
		t.Fatalf("RecordEvent failed: %v", err)
	}

	event := auditRepo.Events[0]
	var decoded map[string]interface{}
	err = json.Unmarshal(event.Details, &decoded)
	if err != nil {
		t.Fatalf("failed to unmarshal details: %v", err)
	}

	if decoded["common_name"] != "example.com" {
		t.Errorf("expected common_name example.com, got %v", decoded["common_name"])
	}
}

func TestList(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		Events: []*domain.AuditEvent{},
	}
	service := NewAuditService(auditRepo)

	for i := 0; i < 5; i++ {
		event := &domain.AuditEvent{
			ID:           "audit-" + string(rune(i)),
			Actor:        "user1",
			ActorType:    domain.ActorTypeUser,
			Action:       "test",
			ResourceType: "certificate",
			ResourceID:   "cert-001",
			Timestamp:    time.Now(),
		}
		auditRepo.AddEvent(event)
	}

	filter := &repository.AuditFilter{
		Page:    1,
		PerPage: 10,
	}

	events, err := service.List(ctx, filter)
	if err != nil {
		t.Fatalf("List failed: %v", err)
	}

	if len(events) != 5 {
		t.Errorf("expected 5 events, got %d", len(events))
	}
}

func TestList_RepoError(t *testing.T) {
	ctx := context.Background()
	auditRepo := &mockAuditRepo{
		ListErr: errNotFound,
	}
	service := NewAuditService(auditRepo)

	filter := &repository.AuditFilter{}

	_, err := service.List(ctx, filter)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}
