package com.lingniu.ingest.eventhistory;

import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader;
import com.lingniu.ingest.tdenginehistory.TdenginePage;
import com.lingniu.ingest.tdenginehistory.TdenginePageCursor;
import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;
import java.time.Instant;
import java.util.List;
import java.util.Locale;

@RestController
@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true")
@ConditionalOnBean(TdengineHistoryReader.class)
@RequestMapping("/api/event-history/jt808")
@Tag(name = "jt-808-raw-frame-controller", description = "JT808 原始帧索引分页查询接口。")
public final class Jt808RawFrameHistoryController {

    private final TdengineHistoryReader reader;

    public Jt808RawFrameHistoryController(TdengineHistoryReader reader) {
        if (reader == null) {
            throw new IllegalArgumentException("reader must not be null");
        }
        this.reader = reader;
    }

    @GetMapping("/raw-frames")
    @Operation(
            summary = "查询 JT808 原始帧索引",
            description = "按 phone、VIN、vehicleKey、messageId、parseStatus 和时间范围查询 TDengine raw_frames。"
                    + "返回完整 metadataJson，包含注册帧字段、身份解析来源、rawArchiveUri 等排障信息。"
                    + "分页使用 cursorTs + cursorId，不使用 offset。")
    public RawFramePageResponse rawFrames(
            @Parameter(description = "JT808 终端手机号或终端标识。", example = "13079963320")
            @RequestParam(required = false) String phone,
            @Parameter(description = "VIN。VIN 已反写后可用该字段查询。", example = "LNVIN000000000001")
            @RequestParam(required = false) String vin,
            @Parameter(description = "内部车辆键；传入后精确过滤 vehicle_key。", example = "jt808:13079963320")
            @RequestParam(required = false) String vehicleKey,
            @Parameter(description = "JT808 消息 ID，支持十进制，例如注册帧 256、位置帧 512。", example = "256")
            @RequestParam(required = false) Integer messageId,
            @Parameter(description = "解析状态，例如 SUCCEEDED、FAILED。", example = "SUCCEEDED")
            @RequestParam(required = false) String parseStatus,
            @Parameter(description = "开始时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-29T13:00:00")
            @RequestParam String dateFrom,
            @Parameter(description = "结束时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-29T13:10:00")
            @RequestParam String dateTo,
            @Parameter(description = "排序方向。", example = "DESC")
            @RequestParam(defaultValue = "DESC") TdengineQueryOrder order,
            @Parameter(description = "返回原始帧数量上限，最大 1000。", example = "100")
            @RequestParam(defaultValue = "100") int limit,
            @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z")
            @RequestParam(required = false) String cursorTs,
            @Parameter(description = "上一页返回的 nextCursor.id。", example = "jt808-frame-xxx")
            @RequestParam(required = false) String cursorId) throws IOException {
        if (allBlank(phone, vin, vehicleKey) && messageId == null && isBlank(parseStatus)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                    "phone, vin, vehicleKey, messageId or parseStatus is required for jt808 raw frame query");
        }
        QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo);
        TdenginePage<TdengineRawFrameRow> page = reader.queryRawFrames(new TdengineRawFrameQuery(
                "JT808",
                normalize(vehicleKey),
                normalize(vin),
                normalize(phone),
                messageId,
                normalize(parseStatus).toUpperCase(Locale.ROOT),
                range.eventTimeFrom(),
                range.eventTimeTo().plusMillis(1),
                order,
                limit,
                cursor(cursorTs, cursorId)));
        return RawFramePageResponse.from(page);
    }

    private static TdenginePageCursor cursor(String cursorTs, String cursorId) {
        boolean hasTs = cursorTs != null && !cursorTs.isBlank();
        boolean hasId = cursorId != null && !cursorId.isBlank();
        if (!hasTs && !hasId) {
            return null;
        }
        if (!hasTs || !hasId) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "cursorTs and cursorId must be provided together");
        }
        return new TdenginePageCursor(Instant.parse(cursorTs.trim()), cursorId.trim());
    }

    private static boolean allBlank(String... values) {
        for (String value : values) {
            if (!isBlank(value)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isBlank(String value) {
        return value == null || value.isBlank();
    }

    private static String normalize(String value) {
        return value == null ? "" : value.trim();
    }

    public record RawFramePageResponse(
            List<RawFrameResponse> items,
            CursorResponse nextCursor) {

        private static RawFramePageResponse from(TdenginePage<TdengineRawFrameRow> page) {
            CursorResponse next = page.nextCursor()
                    .map(cursor -> new CursorResponse(cursor.ts().toString(), cursor.tieBreaker()))
                    .orElse(null);
            return new RawFramePageResponse(
                    page.items().stream().map(RawFrameResponse::from).toList(),
                    next);
        }
    }

    public record CursorResponse(String ts, String id) {
    }

    public record RawFrameResponse(
            String eventTime,
            String receivedAt,
            String frameId,
            int messageId,
            String messageIdHex,
            int subType,
            String rawUri,
            String checksum,
            long rawSizeBytes,
            String parseStatus,
            String parseError,
            String peer,
            String metadataJson,
            String protocol,
            String vehicleKey,
            String vin,
            String phone) {

        private static RawFrameResponse from(TdengineRawFrameRow row) {
            return new RawFrameResponse(
                    row.ts().toString(),
                    row.receivedAt().toString(),
                    row.frameId(),
                    row.messageId(),
                    "0x%04X".formatted(row.messageId()),
                    row.subType(),
                    row.rawUri(),
                    row.checksum(),
                    row.rawSizeBytes(),
                    row.parseStatus(),
                    row.parseError(),
                    row.peer(),
                    row.metadataJson(),
                    row.protocol(),
                    row.vehicleKey(),
                    row.vin(),
                    row.phone());
        }
    }
}
