package ai.asdm.admin.core.controller.metrics;

import ai.asdm.admin.core.dto.auth.ApiResponse;
import ai.asdm.admin.core.dto.auth.ErrorCode;
import ai.asdm.admin.core.dto.metrics.MetricsChartsDto;
import ai.asdm.admin.core.dto.metrics.MetricsPayloadDto;
import ai.asdm.admin.core.dto.metrics.MetricsSummaryDto;
import ai.asdm.admin.core.dto.metrics.OrganizationMetricsRowDto;
import ai.asdm.admin.core.dto.metrics.ProjectMetricsRowDto;
import ai.asdm.admin.core.entity.project.Project;
import ai.asdm.admin.core.entity.project.ProjectCollection;
import ai.asdm.admin.core.service.metrics.MetricsScopeService;
import ai.asdm.admin.core.service.metrics.MetricsService;
import ai.asdm.admin.core.service.project.ProjectCollectionService;
import ai.asdm.admin.core.service.project.ProjectService;
import ai.asdm.admin.core.util.RequestContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;

import java.util.List;
import java.util.Optional;

class MetricsControllerTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Test
    void organizationPayload_shouldExposeNewFieldsAndRejectLegacyContract() throws Exception {
        OrganizationMetricsRowDto row = new OrganizationMetricsRowDto("p-1", "Project A", 3);
        row.setWorkspaceCount(2);
        row.setCreatedAt("2026-04-01 12:00");
        row.setStatus("Active");
        MetricsSummaryDto summary = new MetricsSummaryDto();
        MetricsPayloadDto<OrganizationMetricsRowDto> payload = new MetricsPayloadDto<>(summary, List.of(row), 1, 20, 99L);
        payload.setCharts(new MetricsChartsDto());

        String json = OBJECT_MAPPER.writeValueAsString(payload);
        Assertions.assertTrue(json.contains("\"id\":\"p-1\""));
        Assertions.assertTrue(json.contains("\"name\":\"Project A\""));
        Assertions.assertTrue(json.contains("\"userCount\":3"));
        Assertions.assertTrue(json.contains("\"createdAt\":\"2026-04-01 12:00\""));
        Assertions.assertTrue(json.contains("\"workspaceCount\":2"));
        Assertions.assertTrue(json.contains("\"page\":1"));
        Assertions.assertTrue(json.contains("\"pageSize\":20"));
        Assertions.assertTrue(json.contains("\"total\":99"));
        assertNoLegacyMetricFields(json);
    }

    @Test
    void projectPayload_shouldExposeNewFieldsAndRejectLegacyContract() throws Exception {
        ProjectMetricsRowDto row = new ProjectMetricsRowDto("r-1", "Toolset A", 8);
        row.setVersion("1.0.0");
        row.setRating(null);
        row.setUsages(4);
        row.setStatus("Active");
        MetricsSummaryDto summary = new MetricsSummaryDto();
        MetricsPayloadDto<ProjectMetricsRowDto> payload = new MetricsPayloadDto<>(summary, List.of(row), 0, 10, 1L);
        payload.setCharts(new MetricsChartsDto());

        String json = OBJECT_MAPPER.writeValueAsString(payload);
        Assertions.assertTrue(json.contains("\"id\":\"r-1\""));
        Assertions.assertTrue(json.contains("\"name\":\"Toolset A\""));
        Assertions.assertTrue(json.contains("\"version\":\"1.0.0\""));
        Assertions.assertTrue(json.contains("\"downloads\":8"));
        Assertions.assertFalse(json.contains("\"rating\":100"));
        Assertions.assertTrue(json.contains("\"usages\":4"));
        Assertions.assertTrue(json.contains("\"page\":0"));
        Assertions.assertTrue(json.contains("\"pageSize\":10"));
        Assertions.assertTrue(json.contains("\"total\":1"));
        assertNoLegacyMetricFields(json);
    }

    @Test
    void payload_shouldSupportEmptyItemsWithoutLegacyFields() throws Exception {
        MetricsSummaryDto summary = new MetricsSummaryDto();
        MetricsPayloadDto<ProjectMetricsRowDto> payload = new MetricsPayloadDto<>(summary, List.of(), 0, 10, 0L);

        String json = OBJECT_MAPPER.writeValueAsString(payload);
        Assertions.assertTrue(json.contains("\"items\":[]"));
        Assertions.assertTrue(json.contains("\"total\":0"));
        assertNoLegacyMetricFields(json);
    }

    private static void assertNoLegacyMetricFields(String json) {
        Assertions.assertFalse(json.contains("\"value\""));
        Assertions.assertFalse(json.contains("\"value2\""));
        Assertions.assertFalse(json.contains("\"value3\""));
        Assertions.assertFalse(json.contains("\"valueText\""));
        Assertions.assertFalse(json.contains("\"category\""));
    }

    @Test
    void getServerMetrics_shouldReturnUnauthorized_whenNoUserId() {
        MetricsController controller = createController();

        ResponseEntity<ApiResponse<MetricsPayloadDto<?>>> response = controller.getServerMetrics(0, 10, 7);

        Assertions.assertEquals(ErrorCode.UNAUTHORIZED.getCode(), response.getStatusCode().value());
        ApiResponse<MetricsPayloadDto<?>> body = response.getBody();
        Assertions.assertNotNull(body);
        Assertions.assertEquals(ErrorCode.UNAUTHORIZED.getCode(), body.getCode());
    }

    @Test
    void getServerMetrics_shouldReturnForbidden_whenNotSuperAdmin() {
        FakeMetricsScopeService scopeService = new FakeMetricsScopeService();
        scopeService.isSuperAdmin = false;
        MetricsController controller = createController(null, scopeService, new FakeProjectCollectionService(), new FakeProjectService());
        RequestContext.setUserId("u-1");

        ResponseEntity<ApiResponse<MetricsPayloadDto<?>>> response = controller.getServerMetrics(0, 10, 7);

        Assertions.assertEquals(ErrorCode.FORBIDDEN.getCode(), response.getStatusCode().value());
    }

    @Test
    void getOrganizationMetrics_shouldReturnBadRequest_whenCollectionIdInvalid() {
        MetricsController controller = createController();
        RequestContext.setUserId("u-2");

        ResponseEntity<ApiResponse<MetricsPayloadDto<?>>> response = controller.getOrganizationMetrics(" ", 0, 10, null);

        Assertions.assertEquals(ErrorCode.BAD_REQUEST.getCode(), response.getStatusCode().value());
    }

    @Test
    void getOrganizationMetrics_shouldReturnNotFound_whenCollectionMissing() {
        FakeProjectCollectionService collectionService = new FakeProjectCollectionService();
        collectionService.collectionLookup = Optional.empty();
        MetricsController controller = createController(null, new FakeMetricsScopeService(), collectionService, new FakeProjectService());
        RequestContext.setUserId("u-3");

        ResponseEntity<ApiResponse<MetricsPayloadDto<?>>> response = controller.getOrganizationMetrics("c-404", 0, 10, null);

        Assertions.assertEquals(ErrorCode.COLLECTION_NOT_FOUND.getCode(), response.getStatusCode().value());
    }

    @Test
    void getOrganizationMetrics_shouldReturnForbidden_whenNoCollectionAccess() {
        FakeProjectCollectionService collectionService = new FakeProjectCollectionService();
        FakeMetricsScopeService scopeService = new FakeMetricsScopeService();
        ProjectCollection collection = new ProjectCollection();
        collectionService.collectionLookup = Optional.of(collection);
        scopeService.hasCollectionAccess = false;
        MetricsController controller = createController(null, scopeService, collectionService, new FakeProjectService());
        RequestContext.setUserId("u-4");

        ResponseEntity<ApiResponse<MetricsPayloadDto<?>>> response = controller.getOrganizationMetrics("c-1", 0, 10, null);

        Assertions.assertEquals(ErrorCode.FORBIDDEN.getCode(), response.getStatusCode().value());
    }

    private static MetricsController createController() {
        return createController(null, new FakeMetricsScopeService(), new FakeProjectCollectionService(), new FakeProjectService());
    }

    private static MetricsController createController(MetricsService metricsService,
                                                      MetricsScopeService metricsScopeService,
                                                      ProjectCollectionService projectCollectionService,
                                                      ProjectService projectService) {
        return new MetricsController(metricsService, metricsScopeService, projectCollectionService, projectService);
    }

    private static final class FakeMetricsScopeService extends MetricsScopeService {
        private boolean isSuperAdmin = true;
        private boolean hasCollectionAccess = true;
        private boolean hasProjectAccess = true;

        private FakeMetricsScopeService() {
            super(null);
        }

        @Override
        public boolean hasAccessToCollection(String userId, String collectionId) {
            return hasCollectionAccess;
        }

        @Override
        public boolean hasAccessToProject(String userId, String projectId) {
            return hasProjectAccess;
        }

        @Override
        public boolean isSuperAdmin(String userId) {
            return isSuperAdmin;
        }
    }

    private static final class FakeProjectCollectionService extends ProjectCollectionService {
        private Optional<ProjectCollection> collectionLookup = Optional.of(new ProjectCollection());

        private FakeProjectCollectionService() {
            super(null, null, null, null, null, null);
        }

        @Override
        public Optional<ProjectCollection> findById(String id) {
            return collectionLookup;
        }
    }

    private static final class FakeProjectService extends ProjectService {
        private Optional<Project> projectLookup = Optional.of(new Project());

        private FakeProjectService() {
            super(null, null);
        }

        @Override
        public Optional<Project> findById(String id) {
            return projectLookup;
        }
    }

    @AfterEach
    void tearDown() {
        RequestContext.clear();
    }
}
