package de.gecheckt.pdf.umbenenner.adapter.in.cli;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Instant;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
import de.gecheckt.pdf.umbenenner.domain.model.RunId;

/**
 * Unit tests for {@link SchedulerBatchCommand}.
 * <p>
 * Verifies that the CLI adapter correctly delegates to the batch processing use case
 * and returns the outcome without transformation.
 */
class SchedulerBatchCommandTest {

    @Test
    void constructor_acceptsRunBatchProcessingUseCase() {
        BatchRunProcessingUseCase mockUseCase = (context) -> BatchRunOutcome.SUCCESS;

        SchedulerBatchCommand command = new SchedulerBatchCommand(mockUseCase);

        assertNotNull(command, "Constructor should create a valid SchedulerBatchCommand");
    }

    @Test
    void run_delegatesToUseCaseAndReturnsOutcome() {
        // Setup: mock use case that returns SUCCESS
        BatchRunProcessingUseCase mockUseCase = (context) -> BatchRunOutcome.SUCCESS;

        SchedulerBatchCommand command = new SchedulerBatchCommand(mockUseCase);
        BatchRunContext context = new BatchRunContext(new RunId("test-run"), Instant.now());

        // Execute
        BatchRunOutcome outcome = command.run(context);

        // Verify
        assertEquals(BatchRunOutcome.SUCCESS, outcome);
    }

    @Test
    void run_passesContextToUseCase() {
        RunId expectedRunId = new RunId("test-run-123");
        Instant expectedStartTime = Instant.now();

        // Setup: mock use case that captures the context
        MockCapturingUseCase mockUseCase = new MockCapturingUseCase();

        SchedulerBatchCommand command = new SchedulerBatchCommand(mockUseCase);
        BatchRunContext context = new BatchRunContext(expectedRunId, expectedStartTime);

        // Execute
        command.run(context);

        // Verify context was passed correctly
        assertNotNull(mockUseCase.capturedContext);
        assertEquals(expectedRunId, mockUseCase.capturedContext.runId());
        assertEquals(expectedStartTime, mockUseCase.capturedContext.startInstant());
    }

    @Test
    void run_returnsSuccessOutcome() {
        BatchRunProcessingUseCase successUseCase = (context) -> BatchRunOutcome.SUCCESS;
        SchedulerBatchCommand command = new SchedulerBatchCommand(successUseCase);
        BatchRunContext context = new BatchRunContext(new RunId("test"), Instant.now());

        BatchRunOutcome outcome = command.run(context);

        assertEquals(BatchRunOutcome.SUCCESS, outcome);
        assertTrue(outcome.isSuccess());
        assertFalse(outcome.isFailure());
    }

    @Test
    void run_returnsFailureOutcome() {
        BatchRunProcessingUseCase failureUseCase = (context) -> BatchRunOutcome.FAILURE;
        SchedulerBatchCommand command = new SchedulerBatchCommand(failureUseCase);
        BatchRunContext context = new BatchRunContext(new RunId("test"), Instant.now());

        BatchRunOutcome outcome = command.run(context);

        assertEquals(BatchRunOutcome.FAILURE, outcome);
        assertTrue(outcome.isFailure());
        assertFalse(outcome.isSuccess());
    }

    @Test
    void run_returnsLockUnavailableOutcome() {
        BatchRunProcessingUseCase lockUnavailableUseCase = (context) -> BatchRunOutcome.LOCK_UNAVAILABLE;
        SchedulerBatchCommand command = new SchedulerBatchCommand(lockUnavailableUseCase);
        BatchRunContext context = new BatchRunContext(new RunId("test"), Instant.now());

        BatchRunOutcome outcome = command.run(context);

        assertEquals(BatchRunOutcome.LOCK_UNAVAILABLE, outcome);
        assertTrue(outcome.isLockUnavailable());
    }

    @Test
    void run_multipleCallsWithDifferentContexts() {
        BatchRunProcessingUseCase mockUseCase = (context) -> BatchRunOutcome.SUCCESS;
        SchedulerBatchCommand command = new SchedulerBatchCommand(mockUseCase);

        BatchRunContext context1 = new BatchRunContext(new RunId("run-1"), Instant.now());
        BatchRunContext context2 = new BatchRunContext(new RunId("run-2"), Instant.now());

        // Execute multiple times
        BatchRunOutcome outcome1 = command.run(context1);
        BatchRunOutcome outcome2 = command.run(context2);

        // Verify both succeed independently
        assertEquals(BatchRunOutcome.SUCCESS, outcome1);
        assertEquals(BatchRunOutcome.SUCCESS, outcome2);
    }

    @Test
    void run_delegatesWithoutModifyingContext() {
        RunId runId = new RunId("immutable-test");
        Instant startTime = Instant.parse("2026-03-31T20:00:00Z");

        BatchRunProcessingUseCase mockUseCase = (context) -> {
            // Verify context state is not modified
            assertEquals(runId, context.runId());
            assertEquals(startTime, context.startInstant());
            assertFalse(context.isCompleted());
            return BatchRunOutcome.SUCCESS;
        };

        SchedulerBatchCommand command = new SchedulerBatchCommand(mockUseCase);
        BatchRunContext context = new BatchRunContext(runId, startTime);

        command.run(context);
        // After run, context should still be incomplete (not modified by adapter)
        assertFalse(context.isCompleted());
    }

    /**
     * Mock use case that captures the passed context for verification.
     */
    private static class MockCapturingUseCase implements BatchRunProcessingUseCase {
        BatchRunContext capturedContext;

        @Override
        public BatchRunOutcome execute(BatchRunContext context) {
            this.capturedContext = context;
            return BatchRunOutcome.SUCCESS;
        }
    }
}
