package com.storycove.service;

import com.storycove.config.AudioCoveProperties;
import com.storycove.dto.AudioCoveIngestRequest;
import com.storycove.entity.Author;
import com.storycove.entity.Series;
import com.storycove.entity.Story;
import com.storycove.entity.Tag;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.lang.reflect.Method;
import java.util.UUID;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class AudioCoveServiceTest {

    @Mock
    private StoryService storyService;

    @Mock
    private CollectionService collectionService;

    private AudioCoveProperties audioCoveProperties;
    private AudioCoveService audioCoveService;

    @BeforeEach
    void setUp() {
        audioCoveProperties = new AudioCoveProperties();
        audioCoveProperties.setUrl("http://audiocove:8000");
        audioCoveProperties.setApiKey("test-api-key");
        audioCoveService = new AudioCoveService(storyService, collectionService, audioCoveProperties, "http://localhost:6925");
    }

    @Test
    @DisplayName("Throws IllegalStateException when AudioCove URL is not configured")
    void ingestStory_throwsWhenNotConfigured() {
        audioCoveProperties.setUrl("");
        UUID storyId = UUID.randomUUID();

        assertThatThrownBy(() -> audioCoveService.ingestStory(storyId))
                .isInstanceOf(IllegalStateException.class)
                .hasMessageContaining("AUDIOCOVE_URL");
    }

    @Test
    @DisplayName("Content hash is formatted as sha256:<64-hex-chars>")
    void computeContentHash_correctFormat() throws Exception {
        Method method = AudioCoveService.class.getDeclaredMethod("computeContentHash", String.class);
        method.setAccessible(true);

        String hash = (String) method.invoke(audioCoveService, "hello world");

        assertThat(hash).startsWith("sha256:");
        assertThat(hash.substring(7)).hasSize(64).matches("[0-9a-f]+");
    }

    @Test
    @DisplayName("Null content produces a valid hash (of empty string)")
    void computeContentHash_nullContentProducesValidHash() throws Exception {
        Method method = AudioCoveService.class.getDeclaredMethod("computeContentHash", String.class);
        method.setAccessible(true);

        String hash = (String) method.invoke(audioCoveService, (Object) null);

        assertThat(hash).startsWith("sha256:");
        assertThat(hash.substring(7)).hasSize(64);
    }

    @Test
    @DisplayName("Payload maps all story fields correctly")
    void buildIngestRequest_mapsAllFields() throws Exception {
        UUID storyId = UUID.randomUUID();
        Story story = buildFullStory(storyId);
        when(storyService.findById(storyId)).thenReturn(story);

        // We can't call ingestStory directly without mocking RestClient,
        // so we test buildIngestRequest via reflection
        Method method = AudioCoveService.class.getDeclaredMethod("buildIngestRequest", Story.class);
        method.setAccessible(true);

        AudioCoveIngestRequest request = (AudioCoveIngestRequest) method.invoke(audioCoveService, story);

        assertThat(request.getStoryId()).isEqualTo(storyId.toString());
        assertThat(request.getTitle()).isEqualTo("Test Story");
        assertThat(request.getAuthor()).isEqualTo("Jane Doe");
        assertThat(request.getContentHtml()).isEqualTo("<p>Content</p>");
        assertThat(request.getContentHash()).startsWith("sha256:");
        assertThat(request.getSeries()).isEqualTo("Nightfall Chronicles");
        assertThat(request.getSeriesPart()).isEqualTo("2");
        assertThat(request.getTags()).containsExactlyInAnyOrder("fantasy", "short-story");
        assertThat(request.getCoverImage()).isEqualTo("http://localhost:6925/api/files/images/cover.jpg");
        assertThat(request.getSchemaVersion()).isEqualTo(1);
    }

    @Test
    @DisplayName("Null coverPath results in null coverImage")
    void buildIngestRequest_nullCoverProducesNullCoverImage() throws Exception {
        UUID storyId = UUID.randomUUID();
        Story story = buildFullStory(storyId);
        story.setCoverPath(null);

        Method method = AudioCoveService.class.getDeclaredMethod("buildIngestRequest", Story.class);
        method.setAccessible(true);

        AudioCoveIngestRequest request = (AudioCoveIngestRequest) method.invoke(audioCoveService, story);

        assertThat(request.getCoverImage()).isNull();
    }

    @Test
    @DisplayName("Story without author or series produces null for those fields")
    void buildIngestRequest_nullAuthorAndSeries() throws Exception {
        UUID storyId = UUID.randomUUID();
        Story story = new Story();
        ReflectionTestUtils.setField(story, "id", storyId);
        story.setTitle("Minimal Story");
        story.setContentHtml("<p>Content</p>");

        Method method = AudioCoveService.class.getDeclaredMethod("buildIngestRequest", Story.class);
        method.setAccessible(true);

        AudioCoveIngestRequest request = (AudioCoveIngestRequest) method.invoke(audioCoveService, story);

        assertThat(request.getAuthor()).isNull();
        assertThat(request.getSeries()).isNull();
        assertThat(request.getSeriesPart()).isNull();
    }

    @Test
    @DisplayName("Volume is serialized as string for series_part")
    void buildIngestRequest_volumeAsString() throws Exception {
        UUID storyId = UUID.randomUUID();
        Story story = buildFullStory(storyId);
        story.setVolume(3.0);

        Method method = AudioCoveService.class.getDeclaredMethod("buildIngestRequest", Story.class);
        method.setAccessible(true);

        AudioCoveIngestRequest request = (AudioCoveIngestRequest) method.invoke(audioCoveService, story);

        assertThat(request.getSeriesPart()).isEqualTo("3");
    }

    private Story buildFullStory(UUID storyId) {
        Author author = new Author("Jane Doe");
        Series series = new Series("Nightfall Chronicles");

        Tag tag1 = new Tag("fantasy");
        Tag tag2 = new Tag("short-story");

        Story story = new Story();
        ReflectionTestUtils.setField(story, "id", storyId);
        story.setTitle("Test Story");
        story.setContentHtml("<p>Content</p>");
        story.setAuthor(author);
        story.setSeries(series);
        story.setVolume(2.0);
        story.setCoverPath("cover.jpg");
        story.getTags().add(tag1);
        story.getTags().add(tag2);

        return story;
    }
}
