package service

import (
	"context"
	"errors"
	"testing"

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

// mockProfileRepo is a test implementation of CertificateProfileRepository
type mockProfileRepo struct {
	profiles  map[string]*domain.CertificateProfile
	ListErr   error
	GetErr    error
	CreateErr error
	UpdateErr error
	DeleteErr error
}

func newMockProfileRepository() *mockProfileRepo {
	return &mockProfileRepo{
		profiles: make(map[string]*domain.CertificateProfile),
	}
}

func (m *mockProfileRepo) List(ctx context.Context) ([]*domain.CertificateProfile, error) {
	if m.ListErr != nil {
		return nil, m.ListErr
	}
	var profiles []*domain.CertificateProfile
	for _, p := range m.profiles {
		profiles = append(profiles, p)
	}
	return profiles, nil
}

func (m *mockProfileRepo) Get(ctx context.Context, id string) (*domain.CertificateProfile, error) {
	if m.GetErr != nil {
		return nil, m.GetErr
	}
	p, ok := m.profiles[id]
	if !ok {
		return nil, errNotFound
	}
	return p, nil
}

func (m *mockProfileRepo) Create(ctx context.Context, profile *domain.CertificateProfile) error {
	if m.CreateErr != nil {
		return m.CreateErr
	}
	m.profiles[profile.ID] = profile
	return nil
}

func (m *mockProfileRepo) Update(ctx context.Context, profile *domain.CertificateProfile) error {
	if m.UpdateErr != nil {
		return m.UpdateErr
	}
	m.profiles[profile.ID] = profile
	return nil
}

func (m *mockProfileRepo) Delete(ctx context.Context, id string) error {
	if m.DeleteErr != nil {
		return m.DeleteErr
	}
	delete(m.profiles, id)
	return nil
}

func (m *mockProfileRepo) AddProfile(p *domain.CertificateProfile) {
	m.profiles[p.ID] = p
}

// --- ProfileService Tests ---

func TestProfileService_ListProfiles(t *testing.T) {
	repo := newMockProfileRepository()
	repo.AddProfile(&domain.CertificateProfile{ID: "prof-1", Name: "Standard TLS", Enabled: true})
	repo.AddProfile(&domain.CertificateProfile{ID: "prof-2", Name: "Internal mTLS", Enabled: true})

	svc := NewProfileService(repo, nil)
	profiles, total, err := svc.ListProfiles(1, 50)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if total != 2 {
		t.Errorf("expected total 2, got %d", total)
	}
	if len(profiles) != 2 {
		t.Errorf("expected 2 profiles, got %d", len(profiles))
	}
}

func TestProfileService_ListProfiles_Empty(t *testing.T) {
	repo := newMockProfileRepository()
	svc := NewProfileService(repo, nil)

	profiles, total, err := svc.ListProfiles(1, 50)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if total != 0 {
		t.Errorf("expected total 0, got %d", total)
	}
	if len(profiles) != 0 {
		t.Errorf("expected 0 profiles, got %d", len(profiles))
	}
}

func TestProfileService_ListProfiles_RepoError(t *testing.T) {
	repo := newMockProfileRepository()
	repo.ListErr = errors.New("db error")
	svc := NewProfileService(repo, nil)

	_, _, err := svc.ListProfiles(1, 50)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}

func TestProfileService_GetProfile(t *testing.T) {
	repo := newMockProfileRepository()
	repo.AddProfile(&domain.CertificateProfile{ID: "prof-1", Name: "Standard TLS"})
	svc := NewProfileService(repo, nil)

	profile, err := svc.GetProfile("prof-1")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if profile.Name != "Standard TLS" {
		t.Errorf("expected 'Standard TLS', got '%s'", profile.Name)
	}
}

func TestProfileService_GetProfile_NotFound(t *testing.T) {
	repo := newMockProfileRepository()
	svc := NewProfileService(repo, nil)

	_, err := svc.GetProfile("nonexistent")
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}

func TestProfileService_CreateProfile_Defaults(t *testing.T) {
	repo := newMockProfileRepository()
	auditRepo := newMockAuditRepository()
	auditSvc := NewAuditService(auditRepo)
	svc := NewProfileService(repo, auditSvc)

	profile := domain.CertificateProfile{
		Name:          "New Profile",
		MaxTTLSeconds: 86400,
	}

	created, err := svc.CreateProfile(profile)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if created.ID == "" {
		t.Error("expected generated ID, got empty")
	}
	if len(created.AllowedKeyAlgorithms) == 0 {
		t.Error("expected default key algorithms, got empty")
	}
	if len(created.AllowedEKUs) == 0 {
		t.Error("expected default EKUs, got empty")
	}
	if created.CreatedAt.IsZero() {
		t.Error("expected CreatedAt to be set")
	}
	// Verify audit event recorded
	if len(auditRepo.Events) != 1 {
		t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
	}
}

func TestProfileService_CreateProfile_ValidationErrors(t *testing.T) {
	repo := newMockProfileRepository()
	svc := NewProfileService(repo, nil)

	tests := []struct {
		name    string
		profile domain.CertificateProfile
		errMsg  string
	}{
		{
			name:    "empty name",
			profile: domain.CertificateProfile{},
			errMsg:  "profile name is required",
		},
		{
			name: "name too long",
			profile: domain.CertificateProfile{
				Name: string(make([]byte, 256)),
			},
			errMsg: "exceeds 255 characters",
		},
		{
			name: "invalid key algorithm",
			profile: domain.CertificateProfile{
				Name: "Bad Algo",
				AllowedKeyAlgorithms: []domain.KeyAlgorithmRule{
					{Algorithm: "DES", MinSize: 56},
				},
			},
			errMsg: "invalid key algorithm",
		},
		{
			name: "RSA key too small",
			profile: domain.CertificateProfile{
				Name: "Weak RSA",
				AllowedKeyAlgorithms: []domain.KeyAlgorithmRule{
					{Algorithm: "RSA", MinSize: 1024},
				},
			},
			errMsg: "RSA minimum key size must be at least 2048",
		},
		{
			name: "ECDSA key too small",
			profile: domain.CertificateProfile{
				Name: "Weak ECDSA",
				AllowedKeyAlgorithms: []domain.KeyAlgorithmRule{
					{Algorithm: "ECDSA", MinSize: 128},
				},
			},
			errMsg: "ECDSA minimum key size must be at least 256",
		},
		{
			name: "invalid EKU",
			profile: domain.CertificateProfile{
				Name:        "Bad EKU",
				AllowedEKUs: []string{"invalidEKU"},
			},
			errMsg: "invalid EKU",
		},
		{
			name: "negative TTL",
			profile: domain.CertificateProfile{
				Name:          "Negative TTL",
				MaxTTLSeconds: -1,
			},
			errMsg: "cannot be negative",
		},
		{
			name: "short-lived with long TTL",
			profile: domain.CertificateProfile{
				Name:            "Inconsistent Short-Lived",
				AllowShortLived: true,
				MaxTTLSeconds:   7200,
			},
			errMsg: "short-lived certs must have TTL under 1 hour",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := svc.CreateProfile(tt.profile)
			if err == nil {
				t.Fatalf("expected error containing %q, got nil", tt.errMsg)
			}
			if !contains(err.Error(), tt.errMsg) {
				t.Errorf("expected error containing %q, got %q", tt.errMsg, err.Error())
			}
		})
	}
}

func TestProfileService_CreateProfile_RepoError(t *testing.T) {
	repo := newMockProfileRepository()
	repo.CreateErr = errors.New("db create failed")
	svc := NewProfileService(repo, nil)

	_, err := svc.CreateProfile(domain.CertificateProfile{Name: "Valid"})
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}

func TestProfileService_UpdateProfile(t *testing.T) {
	repo := newMockProfileRepository()
	repo.AddProfile(&domain.CertificateProfile{ID: "prof-1", Name: "Original"})
	auditRepo := newMockAuditRepository()
	auditSvc := NewAuditService(auditRepo)
	svc := NewProfileService(repo, auditSvc)

	updated, err := svc.UpdateProfile("prof-1", domain.CertificateProfile{
		Name:          "Updated",
		MaxTTLSeconds: 43200,
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if updated.ID != "prof-1" {
		t.Errorf("expected ID 'prof-1', got '%s'", updated.ID)
	}
	if len(auditRepo.Events) != 1 {
		t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
	}
}

func TestProfileService_UpdateProfile_ValidationError(t *testing.T) {
	repo := newMockProfileRepository()
	svc := NewProfileService(repo, nil)

	_, err := svc.UpdateProfile("prof-1", domain.CertificateProfile{Name: ""})
	if err == nil {
		t.Fatal("expected validation error, got nil")
	}
}

func TestProfileService_DeleteProfile(t *testing.T) {
	repo := newMockProfileRepository()
	repo.AddProfile(&domain.CertificateProfile{ID: "prof-1", Name: "To Delete"})
	auditRepo := newMockAuditRepository()
	auditSvc := NewAuditService(auditRepo)
	svc := NewProfileService(repo, auditSvc)

	err := svc.DeleteProfile("prof-1")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(auditRepo.Events) != 1 {
		t.Errorf("expected 1 audit event, got %d", len(auditRepo.Events))
	}
}

func TestProfileService_DeleteProfile_RepoError(t *testing.T) {
	repo := newMockProfileRepository()
	repo.DeleteErr = errors.New("db delete failed")
	svc := NewProfileService(repo, nil)

	err := svc.DeleteProfile("prof-1")
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}

func TestProfileService_CreateProfile_ValidShortLived(t *testing.T) {
	repo := newMockProfileRepository()
	svc := NewProfileService(repo, nil)

	// Short-lived with TTL under 1 hour should succeed
	created, err := svc.CreateProfile(domain.CertificateProfile{
		Name:            "CI Ephemeral",
		AllowShortLived: true,
		MaxTTLSeconds:   300, // 5 minutes
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !created.AllowShortLived {
		t.Error("expected AllowShortLived to be true")
	}
}

func TestIsShortLived(t *testing.T) {
	tests := []struct {
		name     string
		profile  domain.CertificateProfile
		expected bool
	}{
		{
			name:     "short-lived with 5 min TTL",
			profile:  domain.CertificateProfile{AllowShortLived: true, MaxTTLSeconds: 300},
			expected: true,
		},
		{
			name:     "short-lived flag false",
			profile:  domain.CertificateProfile{AllowShortLived: false, MaxTTLSeconds: 300},
			expected: false,
		},
		{
			name:     "zero TTL with flag",
			profile:  domain.CertificateProfile{AllowShortLived: true, MaxTTLSeconds: 0},
			expected: false,
		},
		{
			name:     "TTL at 1 hour boundary",
			profile:  domain.CertificateProfile{AllowShortLived: true, MaxTTLSeconds: 3600},
			expected: false,
		},
		{
			name:     "standard long-lived",
			profile:  domain.CertificateProfile{AllowShortLived: false, MaxTTLSeconds: 7776000},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.profile.IsShortLived()
			if result != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, result)
			}
		})
	}
}

// contains checks if a string contains a substring (helper for test assertions).
func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstring(s, substr))
}

func containsSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
