package com.mosquito.project.integration;

import com.mosquito.project.dto.CreateActivityRequest;
import com.mosquito.project.dto.CreateApiKeyRequest;
import com.mosquito.project.security.IntrospectionResponse;
import com.mosquito.project.security.UserIntrospectionService;
import com.mosquito.project.service.ActivityService;
import com.mosquito.project.web.UrlValidator;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

/**
 * 用户操作完整流程集成测试
 * 覆盖与当前服务实现一致的核心旅程
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@DisplayName("用户操作完整流程测试")
@Tag("journey")
@EnabledIfSystemProperty(named = "journey.test.enabled", matches = "true")
public class UserOperationJourneyTest {

    @LocalServerPort
    private int port;

    @Autowired
    private ActivityService activityService;

    @MockBean
    private UserIntrospectionService userIntrospectionService;

    @MockBean
    private UrlValidator urlValidator;

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
            .withDatabaseName("mosquito_test")
            .withUsername("test")
            .withPassword("test");

    @Container
    static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
            .withExposedPorts(6379);

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
        registry.add("spring.datasource.driver-class-name", () -> "org.postgresql.Driver");
        registry.add("spring.redis.host", redis::getHost);
        registry.add("spring.redis.port", () -> redis.getMappedPort(6379).toString());
    }

    private String userToken;
    private Long userId;
    private String apiKey;
    private Long activityId;

    @BeforeEach
    void setUp() {
        RestAssured.reset();
        RestAssured.baseURI = "http://localhost";
        RestAssured.port = port;
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();

        userToken = "test-user-token";
        userId = 1001L;

        ensureApiKey();
        stubSecurity();

        RestAssured.requestSpecification = new RequestSpecBuilder()
            .addHeader("X-API-Key", apiKey)
            .addHeader("Authorization", "Bearer " + userToken)
            .build();
    }

    @Nested
    @DisplayName("活动与统计")
    class ActivityFlow {

        @Test
        @DisplayName("活动列表与统计数据")
        void testActivityQueries() {
            given()
            .when()
                .get("/api/v1/activities")
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.id", hasItem(activityId.intValue()));

            given()
            .when()
                .get("/api/v1/activities/" + activityId)
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.id", is(activityId.intValue()));

            given()
            .when()
                .get("/api/v1/activities/" + activityId + "/stats")
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.totalParticipants", notNullValue())
                .body("data.totalShares", notNullValue());

            given()
            .when()
                .get("/api/v1/activities/" + activityId + "/graph")
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.nodes", notNullValue())
                .body("data.edges", notNullValue());

            given()
            .when()
                .get("/api/v1/activities/" + activityId + "/leaderboard?page=0&size=10")
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("meta.pagination.page", is(0))
                .body("meta.pagination.size", is(10));
        }
    }

    @Nested
    @DisplayName("API Key")
    class ApiKeyFlow {

        @Test
        @DisplayName("创建并校验 API Key")
        void testApiKeyLifecycle() {
            Map<String, Object> createRequest = new HashMap<>();
            createRequest.put("activityId", activityId);
            createRequest.put("name", "Journey Key");

            Response createResponse = given()
                .contentType(ContentType.JSON)
                .body(createRequest)
            .when()
                .post("/api/v1/api-keys")
            .then()
                .statusCode(201)
                .body("code", is(201))
                .body("data.apiKey", notNullValue())
                .extract()
                .response();

            String newApiKey = createResponse.jsonPath().getString("data.apiKey");
            Map<String, Object> validateRequest = new HashMap<>();
            validateRequest.put("apiKey", newApiKey);

            given()
                .contentType(ContentType.JSON)
                .body(validateRequest)
            .when()
                .post("/api/v1/api-keys/validate")
            .then()
                .statusCode(200)
                .body("code", is(200));
        }
    }

    @Nested
    @DisplayName("用户体验")
    class UserExperienceFlow {

        @Test
        @DisplayName("邀请信息与分享配置")
        void testInvitationInfoAndPosterConfig() {
            given()
            .when()
                .get("/api/v1/me/invitation-info?activityId=" + activityId + "&userId=" + userId)
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.path", startsWith("/r/"))
                .body("data.originalUrl", containsString("activityId=" + activityId));

            given()
            .when()
                .get("/api/v1/me/share-meta?activityId=" + activityId + "&userId=" + userId)
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.title", notNullValue())
                .body("data.description", notNullValue())
                .body("data.image", notNullValue())
                .body("data.url", notNullValue());

            given()
            .when()
                .get("/api/v1/me/poster/config")
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.imageUrl", containsString("/api/v1/me/poster/image"))
                .body("data.htmlUrl", containsString("/api/v1/me/poster/html"));
        }
    }

    @Nested
    @DisplayName("短链与分享指标")
    class ShortLinkFlow {

        @Test
        @DisplayName("短链跳转与分享指标")
        void testShortLinkAndMetrics() {
            String originalUrl = "https://example.com/landing?activityId=" + activityId + "&inviter=" + userId;
            Map<String, Object> shortenRequest = new HashMap<>();
            shortenRequest.put("originalUrl", originalUrl);

            Response shortenResponse = given()
                .contentType(ContentType.JSON)
                .body(shortenRequest)
            .when()
                .post("/api/v1/internal/shorten")
            .then()
                .statusCode(201)
                .body("code", notNullValue())
                .body("path", startsWith("/r/"))
                .extract()
                .response();

            String code = shortenResponse.jsonPath().getString("code");

            given()
                .redirects().follow(false)
                .header("User-Agent", "journey-test")
                .header("Referer", "https://example.com")
            .when()
                .get("/r/" + code)
            .then()
                .statusCode(302)
                .header("Location", is(originalUrl));

            given()
            .when()
                .get("/api/v1/share/metrics?activityId=" + activityId)
            .then()
                .statusCode(200)
                .body("code", is(200))
                .body("data.totalClicks", greaterThanOrEqualTo(1));
        }
    }

    private void stubSecurity() {
        IntrospectionResponse response = new IntrospectionResponse();
        response.setActive(true);
        response.setUserId(String.valueOf(userId));
        response.setTenantId("test-tenant");
        response.setRoles(List.of("USER"));
        response.setScopes(List.of("share:read"));
        response.setIat(Instant.now().getEpochSecond());
        response.setExp(Instant.now().plusSeconds(3600).getEpochSecond());
        response.setJti("test-jti");
        when(userIntrospectionService.introspect(anyString())).thenReturn(response);
        when(urlValidator.isAllowedUrl(anyString())).thenReturn(true);
    }

    private void ensureApiKey() {
        if (apiKey != null) {
            return;
        }
        CreateActivityRequest request = new CreateActivityRequest();
        request.setName("集成测试活动");
        request.setStartTime(ZonedDateTime.now().plusHours(1));
        request.setEndTime(ZonedDateTime.now().plusDays(7));

        var activity = activityService.createActivity(request);
        activityId = activity.getId();

        CreateApiKeyRequest apiKeyRequest = new CreateApiKeyRequest();
        apiKeyRequest.setActivityId(activityId);
        apiKeyRequest.setName("集成测试密钥");
        apiKey = activityService.generateApiKey(apiKeyRequest);
    }
}
