package io.cameleer.server.app.web;

import io.cameleer.server.core.runtime.AppService;
import io.cameleer.server.core.runtime.AppVersion;
import io.cameleer.server.core.storage.ArtifactCoordinates;
import io.cameleer.server.core.storage.ArtifactStore;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.io.ByteArrayInputStream;
import java.util.Optional;
import java.util.UUID;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * Unit-level test for {@link ArtifactDownloadController} using
 * {@link MockMvcBuilders#standaloneSetup}. The auth check IS the controller —
 * see the controller class doc and {@code SecurityConfig} permitAll for
 * {@code /api/v1/artifacts/**} — so there is no security context to load.
 * Standalone setup also avoids dragging in {@code WebConfig}'s interceptors
 * (audit / usage tracking), which otherwise pull the full bean graph.
 */
class ArtifactDownloadControllerTest {

    private AppService appService;
    private ArtifactStore artifactStore;
    private ArtifactDownloadTokenSigner signer;
    private MockMvc mvc;

    @BeforeEach
    void setUp() {
        appService = mock(AppService.class);
        artifactStore = mock(ArtifactStore.class);
        signer = mock(ArtifactDownloadTokenSigner.class);
        ArtifactDownloadController controller =
                new ArtifactDownloadController(appService, artifactStore, signer);
        mvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    void streamsBytesWhenSignatureValid() throws Exception {
        UUID id = UUID.randomUUID();
        when(signer.verify(eq(id), eq(123L), eq("good"))).thenReturn(true);
        AppVersion v = new AppVersion(id, UUID.randomUUID(), 1, "loc", "h", "a.jar", 5L, null, null, null);
        when(appService.findVersion(id)).thenReturn(Optional.of(v));
        when(appService.coordinatesFor(v)).thenReturn(new ArtifactCoordinates("default", v.appId(), 1));
        when(artifactStore.size(any())).thenReturn(5L);
        when(artifactStore.get(any())).thenReturn(new ByteArrayInputStream("hello".getBytes()));

        mvc.perform(get("/api/v1/artifacts/{id}", id).param("exp", "123").param("sig", "good"))
                .andExpect(status().isOk())
                .andExpect(header().string("Content-Type", "application/java-archive"))
                .andExpect(content().bytes("hello".getBytes()));
    }

    @Test
    void rejectsBadSignature() throws Exception {
        UUID id = UUID.randomUUID();
        when(signer.verify(any(), anyLong(), any())).thenReturn(false);

        mvc.perform(get("/api/v1/artifacts/{id}", id).param("exp", "123").param("sig", "bad"))
                .andExpect(status().isUnauthorized());

        // Defence-in-depth: bad sig must short-circuit before any data lookup.
        verify(appService, never()).findVersion(any());
    }

    @Test
    void returns404WhenArtifactMissing() throws Exception {
        UUID id = UUID.randomUUID();
        when(signer.verify(any(), anyLong(), any())).thenReturn(true);
        when(appService.findVersion(id)).thenReturn(Optional.empty());

        mvc.perform(get("/api/v1/artifacts/{id}", id).param("exp", "123").param("sig", "ok"))
                .andExpect(status().isNotFound());
    }
}
