package audit

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"

	coremodel "security-center-backend/internal/model"
	"security-center-backend/internal/repository"
	"security-center-backend/internal/service/directory"
)

type fakeAuditRepository struct {
	logs []coremodel.AuditLog
	last *coremodel.AuditLog
}

func (f *fakeAuditRepository) Create(ctx context.Context, log *coremodel.AuditLog) error {
	if log != nil {
		copyLog := *log
		f.last = &copyLog
	}
	return nil
}

func (f *fakeAuditRepository) List(ctx context.Context, filter repository.AuditFilter, page, pageSize int) ([]coremodel.AuditLog, int64, error) {
	return append([]coremodel.AuditLog(nil), f.logs...), int64(len(f.logs)), nil
}

func (f *fakeAuditRepository) FindByID(ctx context.Context, id uint64) (*coremodel.AuditLog, error) {
	if len(f.logs) == 0 {
		return nil, errors.New("not found")
	}
	log := f.logs[0]
	return &log, nil
}

func (f *fakeAuditRepository) Stats(ctx context.Context) (*repository.AuditStats, error) {
	return &repository.AuditStats{}, nil
}

type fakeEmployeeDirectory struct {
	employees []directory.Employee
}

func (f *fakeEmployeeDirectory) FindByID(ctx context.Context, id uint64) (*directory.Employee, error) {
	for i := range f.employees {
		if f.employees[i].ID == id {
			emp := f.employees[i]
			return &emp, nil
		}
	}
	return nil, errors.New("not found")
}

func (f *fakeEmployeeDirectory) FindByEmail(ctx context.Context, email string) (*directory.Employee, error) {
	for i := range f.employees {
		if f.employees[i].Email == email {
			emp := f.employees[i]
			return &emp, nil
		}
	}
	return nil, errors.New("not found")
}

func (f *fakeEmployeeDirectory) FindByEmails(ctx context.Context, emails []string) ([]directory.Employee, error) {
	seen := make([]directory.Employee, 0, len(emails))
	for _, email := range emails {
		if emp, err := f.FindByEmail(ctx, email); err == nil && emp != nil {
			seen = append(seen, *emp)
		}
	}
	return seen, nil
}

func (f *fakeEmployeeDirectory) SearchEmployees(ctx context.Context, query string) ([]directory.Employee, error) {
	return append([]directory.Employee(nil), f.employees...), nil
}

func (f *fakeEmployeeDirectory) ListEmployees(ctx context.Context) ([]directory.Employee, error) {
	return append([]directory.Employee(nil), f.employees...), nil
}

type fakeUserLookup struct {
	users map[uint64]*coremodel.User
}

func (f *fakeUserLookup) FindByID(ctx context.Context, id uint64) (*coremodel.User, error) {
	if f == nil {
		return nil, errors.New("not found")
	}
	if user, ok := f.users[id]; ok && user != nil {
		copyUser := *user
		return &copyUser, nil
	}
	return nil, errors.New("not found")
}

func TestServiceListEnrichesActorEmployeeDepartmentName(t *testing.T) {
	ctx := context.Background()
	repo := &fakeAuditRepository{
		logs: []coremodel.AuditLog{{
			Base:         coremodel.Base{ID: 1},
			ActorType:    "user",
			ActorEmail:   "alice@example.com",
			Action:       "update",
			ResourceType: "vulnerability",
		}},
	}
	employees := &fakeEmployeeDirectory{
		employees: []directory.Employee{{
			ID:             10,
			Name:           "Alice Chen",
			Email:          "alice@example.com",
			DepartmentID:   "dept-security",
			DepartmentName: "Security Platform",
		}},
	}
	svc := NewService(repo, nil, employees, 1, nil)

	logs, total, err := svc.List(ctx, repository.AuditFilter{}, 1, 20)
	require.NoError(t, err)
	require.Equal(t, int64(1), total)
	require.Len(t, logs, 1)
	require.NotNil(t, logs[0].ActorEmployee)
	require.Equal(t, uint64(10), logs[0].ActorEmployee.ID)
	require.Equal(t, "Security Platform", logs[0].ActorEmployee.DepartmentName)
}

func TestServiceLogDropsMissingUserForeignKey(t *testing.T) {
	ctx := context.Background()
	repo := &fakeAuditRepository{}
	users := &fakeUserLookup{users: map[uint64]*coremodel.User{}}
	svc := NewService(repo, users, nil, 1, nil)

	svc.Log(ctx, Entry{
		UserID:       uint64Ptr(99),
		ActorEmail:   "dev.user@example.com",
		Action:       "test",
		ResourceType: "caasm",
	})

	require.NotNil(t, repo.last)
	require.Nil(t, repo.last.UserID)
	require.Nil(t, repo.last.ActorUserID)
	require.Equal(t, "dev.user@example.com", repo.last.ActorEmail)
	require.Equal(t, "user", repo.last.ActorType)
}

func TestServiceLogKeepsValidUserForeignKey(t *testing.T) {
	ctx := context.Background()
	repo := &fakeAuditRepository{}
	users := &fakeUserLookup{users: map[uint64]*coremodel.User{
		1: &coremodel.User{Base: coremodel.Base{ID: 1}, Email: "dev.user@example.com"},
	}}
	svc := NewService(repo, users, nil, 1, nil)

	svc.Log(ctx, Entry{
		UserID:       uint64Ptr(1),
		Action:       "test",
		ResourceType: "caasm",
	})

	require.NotNil(t, repo.last)
	require.NotNil(t, repo.last.UserID)
	require.Equal(t, uint64(1), *repo.last.UserID)
	require.NotNil(t, repo.last.ActorUserID)
	require.Equal(t, uint64(1), *repo.last.ActorUserID)
}

func uint64Ptr(v uint64) *uint64 { return &v }
