package de.gecheckt.pdf.umbenenner.domain.model;

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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Instant;

import org.junit.jupiter.api.Test;

/**
 * Unit tests for {@link BatchRunContext}.
 * <p>
 * Verifies correct modeling of batch run lifecycle including initialization,
 * timestamp tracking, completion marking, and state validation.
 */
class BatchRunContextTest {

    @Test
    void constructor_createsContextWithRunIdAndStartTime() {
        RunId runId = new RunId("test-run");
        Instant startTime = Instant.now();

        BatchRunContext context = new BatchRunContext(runId, startTime);

        assertNotNull(context);
        assertEquals(runId, context.runId());
        assertEquals(startTime, context.startInstant());
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenRunIdIsNull() {
        Instant startTime = Instant.now();
        assertThrows(NullPointerException.class, () -> new BatchRunContext(null, startTime),
                "Constructor should throw NullPointerException when RunId is null");
    }

    @Test
    void constructor_throwsNullPointerExceptionWhenStartInstantIsNull() {
        RunId runId = new RunId("test-run");
        assertThrows(NullPointerException.class, () -> new BatchRunContext(runId, null),
                "Constructor should throw NullPointerException when start instant is null");
    }

    @Test
    void runId_returnsTheConstructorRunId() {
        RunId expectedRunId = new RunId("run-123");
        Instant startTime = Instant.now();
        BatchRunContext context = new BatchRunContext(expectedRunId, startTime);

        assertEquals(expectedRunId, context.runId());
    }

    @Test
    void startInstant_returnsTheConstructorStartTime() {
        RunId runId = new RunId("test-run");
        Instant expectedStartTime = Instant.parse("2026-03-31T20:00:00Z");
        BatchRunContext context = new BatchRunContext(runId, expectedStartTime);

        assertEquals(expectedStartTime, context.startInstant());
    }

    @Test
    void endInstant_isNullImmediatelyAfterConstruction() {
        RunId runId = new RunId("test-run");
        Instant startTime = Instant.now();
        BatchRunContext context = new BatchRunContext(runId, startTime);

        assertNull(context.endInstant(), "End instant should be null immediately after construction");
    }

    @Test
    void isCompleted_returnsFalseWhenEndInstantNotSet() {
        RunId runId = new RunId("test-run");
        Instant startTime = Instant.now();
        BatchRunContext context = new BatchRunContext(runId, startTime);

        assertFalse(context.isCompleted(), "isCompleted should return false when end instant is not set");
    }

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

        BatchRunContext context = new BatchRunContext(runId, startTime);
        context.setEndInstant(endTime);

        assertEquals(endTime, context.endInstant());
    }

    @Test
    void setEndInstant_throwsNullPointerExceptionWhenEndInstantIsNull() {
        RunId runId = new RunId("test-run");
        Instant startTime = Instant.now();
        BatchRunContext context = new BatchRunContext(runId, startTime);

        assertThrows(NullPointerException.class, () -> context.setEndInstant(null),
                "setEndInstant should throw NullPointerException when end instant is null");
    }

    @Test
    void setEndInstant_throwsIllegalStateExceptionWhenAlreadySet() {
        RunId runId = new RunId("test-run");
        Instant startTime = Instant.now();
        Instant firstEndTime = Instant.now().plusSeconds(60);
        Instant secondEndTime = Instant.now().plusSeconds(120);

        BatchRunContext context = new BatchRunContext(runId, startTime);
        context.setEndInstant(firstEndTime);

        assertThrows(IllegalStateException.class, () -> context.setEndInstant(secondEndTime),
                "setEndInstant should throw IllegalStateException when called a second time");
    }

    @Test
    void isCompleted_returnsTrueAfterSetEndInstant() {
        RunId runId = new RunId("test-run");
        Instant startTime = Instant.now();
        Instant endTime = Instant.now().plusSeconds(60);

        BatchRunContext context = new BatchRunContext(runId, startTime);
        context.setEndInstant(endTime);

        assertTrue(context.isCompleted(), "isCompleted should return true after setting end instant");
    }

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

        BatchRunContext context = new BatchRunContext(runId, startTime);
        String representation = context.toString();

        assertTrue(representation.contains("test-run-001"), "toString should contain RunId");
        assertTrue(representation.contains("2026-03-31"), "toString should contain timestamp information");
    }

    @Test
    void contextLifecycle_startToCompletion() {
        RunId runId = new RunId("test-run-lifecycle");
        Instant startTime = Instant.parse("2026-03-31T20:00:00Z");
        Instant endTime = Instant.parse("2026-03-31T20:05:00Z");

        // Create context
        BatchRunContext context = new BatchRunContext(runId, startTime);
        assertFalse(context.isCompleted());

        // Complete context
        context.setEndInstant(endTime);
        assertTrue(context.isCompleted());

        // Verify all data
        assertEquals(runId, context.runId());
        assertEquals(startTime, context.startInstant());
        assertEquals(endTime, context.endInstant());
    }

    @Test
    void multipleContextsAreIndependent() {
        RunId runId1 = new RunId("run-1");
        RunId runId2 = new RunId("run-2");
        Instant startTime1 = Instant.parse("2026-03-31T20:00:00Z");
        Instant startTime2 = Instant.parse("2026-03-31T21:00:00Z");

        BatchRunContext context1 = new BatchRunContext(runId1, startTime1);
        BatchRunContext context2 = new BatchRunContext(runId2, startTime2);

        Instant endTime1 = Instant.parse("2026-03-31T20:05:00Z");
        context1.setEndInstant(endTime1);

        assertTrue(context1.isCompleted());
        assertFalse(context2.isCompleted(), "Context2 should not be affected by Context1's completion");
    }
}
