package service

import (
	"bytes"
	"context"
	"errors"
	"log/slog"
	"strings"
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"

	coremodel "security-center-backend/internal/model"
	"security-center-backend/internal/modules/caasm/mocks"
	caasmmodel "security-center-backend/internal/modules/caasm/model"
	"security-center-backend/internal/modules/caasm/repository"
	"security-center-backend/internal/pkg/logger"
)

type failingSyncJobEventRepo struct {
	err          error
	createCtxErr error
}

func (r *failingSyncJobEventRepo) Create(ctx context.Context, _ *caasmmodel.SyncJobEvent) error {
	r.createCtxErr = ctx.Err()
	return r.err
}

func (r *failingSyncJobEventRepo) List(context.Context, uint64, repository.SyncJobEventListOptions) ([]caasmmodel.SyncJobEvent, error) {
	return nil, nil
}

func TestRemainingHardeningRecordJobEventLogsCreateFailure(t *testing.T) {
	var buf bytes.Buffer
	svc := NewCloudAccountService(nil, nil, nil)
	svc.SetLogger(&logger.Logger{Logger: slog.New(slog.NewTextHandler(&buf, nil))})
	svc.SetEventRepository(&failingSyncJobEventRepo{err: errors.New("insert failed")})

	svc.recordJobEvent(context.Background(), 77, "error", "Persist failed", map[string]interface{}{
		"phase":            "persist",
		"cloud_account_id": uint64(42),
	})

	logged := buf.String()
	require.Contains(t, logged, "failed to record CAASM sync job event")
	require.Contains(t, logged, "job_id=77")
	require.Contains(t, logged, "event_type=error")
	require.Contains(t, logged, "phase=persist")
	require.Contains(t, logged, "insert failed")
}

func TestRemainingHardeningDBEventReporterLogsCreateFailureWithDurableContext(t *testing.T) {
	var buf bytes.Buffer
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	events := &failingSyncJobEventRepo{err: errors.New("event store down")}
	reporter := &DBEventReporter{
		repo:      events,
		syncJobID: 88,
		ctx:       ctx,
		logger:    &logger.Logger{Logger: slog.New(slog.NewTextHandler(&buf, nil))},
	}

	reporter.Report("warning", "Job completed with partial failures", map[string]interface{}{
		"failed_phase": "collect",
	})

	require.NoError(t, events.createCtxErr)
	logged := buf.String()
	require.Contains(t, logged, "failed to record CAASM sync job event")
	require.Contains(t, logged, "job_id=88")
	require.Contains(t, logged, "failed_phase=collect")
	require.Contains(t, logged, "event store down")
}

func TestRemainingHardeningPublicOnlyDomainScopeFiltersPayload(t *testing.T) {
	svc := NewCloudAccountService(nil, nil, nil)
	svc.SetConfigService(NewConfigService(&stubConfigRepoInternal{
		cfg: &caasmmodel.Config{
			Data: coremodel.JSONB{
				"collection_scope": map[string]interface{}{
					"domain_collection_scope": string(caasmmodel.CollectionScopePublicOnly),
				},
			},
		},
	}))

	payload := &caasmmodel.IngestPayload{
		DomainAssets: []caasmmodel.DomainAsset{
			{DomainName: "private.example.test", ZoneType: "private"},
			{DomainName: "public.example.test", ZoneType: "public"},
			{DomainName: "both.example.test", ZoneType: "public-and-private"},
			{DomainName: "unknown.example.test"},
		},
	}

	svc.filterSyncPayloadByCollectionScope(context.Background(), payload)

	require.Len(t, payload.DomainAssets, 2)
	require.Equal(t, "public.example.test", payload.DomainAssets[0].DomainName)
	require.Equal(t, "both.example.test", payload.DomainAssets[1].DomainName)
}

func TestRemainingHardeningPublicOnlyDomainScopePrunesPersistedPrivateDomains(t *testing.T) {
	ctx := context.Background()
	assetRepo := new(mocks.MockAssetRepository)
	svc := NewCloudAccountService(nil, nil, assetRepo)
	svc.SetConfigService(NewConfigService(&stubConfigRepoInternal{
		cfg: &caasmmodel.Config{
			Data: coremodel.JSONB{
				"collection_scope": map[string]interface{}{
					"domain_collection_scope": string(caasmmodel.CollectionScopePublicOnly),
				},
			},
		},
	}))

	assetRepo.On("DeletePrivateDomainAssetsForCloudAccount", mock.Anything, uint64(42)).Return(nil).Once()

	require.NoError(t, svc.prunePersistedDataByCollectionScope(ctx, 42))
	assetRepo.AssertExpectations(t)
}

func TestRemainingHardeningSyncJobStateContract(t *testing.T) {
	tests := []struct {
		status    caasmmodel.SyncJobStatus
		startable bool
		terminal  bool
	}{
		{status: caasmmodel.SyncJobQueued, startable: true},
		{status: caasmmodel.SyncJobPending, startable: true},
		{status: caasmmodel.SyncJobRunning},
		{status: caasmmodel.SyncJobSuccess, terminal: true},
		{status: caasmmodel.SyncJobPartial, terminal: true},
		{status: caasmmodel.SyncJobFailed, terminal: true},
	}

	for _, tt := range tests {
		t.Run(string(tt.status), func(t *testing.T) {
			require.Equal(t, tt.startable, isSyncJobStartableStatus(tt.status))
			require.Equal(t, tt.terminal, isSyncJobTerminalStatus(tt.status))
		})
	}
}

func TestRemainingHardeningIngestDoesNotOverwriteTerminalJob(t *testing.T) {
	ctx := context.Background()
	assetRepo := new(mocks.MockAssetRepository)
	accountRepo := new(mocks.MockCloudAccountRepository)
	jobRepo := &phase2SyncJobRepo{
		job: &caasmmodel.SyncJob{
			Base:   coremodel.Base{ID: 91},
			Status: caasmmodel.SyncJobSuccess,
		},
	}
	eventRepo := &stubSyncJobEventRepo{}
	svc := NewCloudAccountService(accountRepo, jobRepo, assetRepo)
	svc.SetEventRepository(eventRepo)

	err := svc.IngestSyncData(ctx, &caasmmodel.IngestPayload{
		SyncJobID:      91,
		CloudAccountID: 42,
	})

	require.NoError(t, err)
	require.False(t, jobRepo.updateFieldsCalled)
	require.Len(t, eventRepo.events, 1)
	require.Contains(t, eventRepo.events[0].Message, "already terminal")
	require.True(t, strings.EqualFold(string(eventRepo.events[0].Type), "warning"))
}
