package com.lingniu.ingest.eventhistory;

import com.lingniu.ingest.tdenginehistory.TdenginePageCursor;
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;

/**
 * GB32960 历史查询 HTTP 边界。
 *
 * <p>这个 Controller 只做参数校验、时间范围解析和 Swagger 说明，真正的 RAW 读取、
 * 协议解码、snapshot 合并、字段投影都放在 {@link Gb32960DecodedFrameService}。
 * 这样可以保证接口层足够薄，后续如果要把查询服务拆出去，只需要迁移 service。
 */
@RestController
@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true")
@ConditionalOnBean(Gb32960DecodedFrameService.class)
@RequestMapping("/api/event-history/gb32960")
@Tag(name = "gb-32960-frame-controller", description = "GB32960 专用历史帧、业务快照、字段查询和字段字典接口。")
public final class Gb32960FrameController {

    private final Gb32960DecodedFrameService service;

    public Gb32960FrameController(Gb32960DecodedFrameService service) {
        if (service == null) {
            throw new IllegalArgumentException("service must not be null");
        }
        this.service = service;
    }

    @GetMapping("/frames")
    @Operation(
            summary = "查询 GB32960 原始帧解析结果",
            description = "按时间范围查询某辆车或账号下的 GB32960 原始帧，并返回每帧解析出的整包 blocks。主要用于排查原始报文、解析结果和子包内容。")
    public List<Gb32960DecodedFrameService.DecodedFrame> frames(
            @Parameter(description = "开始时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:48:00")
            @RequestParam String dateFrom,
            @Parameter(description = "结束时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:49:00")
            @RequestParam String dateTo,
            @Parameter(description = "排序方向。", example = "DESC")
            @RequestParam(defaultValue = "DESC") HistoryQueryOrder order,
            @Parameter(description = "返回帧数量上限。", example = "10")
            @RequestParam(defaultValue = "10") int limit,
            @Parameter(description = "车辆 VIN；用于缩小到单车查询。", example = "LB9A32A20P0LS1257")
            @RequestParam(required = false) String vin,
            @Parameter(description = "平台账号；用于选择对应厂商扩展解析规则。", example = "Hyundai")
            @RequestParam(required = false) String platformAccount) throws IOException {
        QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo);
        return service.query(
                range.dateFrom(),
                range.dateTo(),
                range.eventTimeFrom(),
                range.eventTimeTo(),
                order,
                limit,
                vin,
                platformAccount);
    }

    @GetMapping("/frames/page")
    @Operation(
            summary = "分页查询 GB32960 原始帧解析结果",
            description = "按 VIN 和时间范围分页查询 TDengine raw_frames，并回读 archive 原始包即时解析。"
                    + "分页使用 cursorTs + cursorId，不使用 offset；适合生产热历史连续翻页。")
    public Gb32960DecodedFrameService.DecodedFramePage framesPage(
            @Parameter(description = "开始时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:48:00")
            @RequestParam String dateFrom,
            @Parameter(description = "结束时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:49:00")
            @RequestParam String dateTo,
            @Parameter(description = "排序方向。", example = "DESC")
            @RequestParam(defaultValue = "DESC") HistoryQueryOrder order,
            @Parameter(description = "返回帧数量上限，最大 1000。", example = "100")
            @RequestParam(defaultValue = "100") int limit,
            @Parameter(description = "车辆 VIN，必填；用于命中 TDengine raw child table。", required = true, example = "LB9A32A20P0LS1257")
            @RequestParam String vin,
            @Parameter(description = "平台账号；用于选择对应厂商扩展解析规则。", example = "Hyundai")
            @RequestParam(required = false) String platformAccount,
            @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z")
            @RequestParam(required = false) String cursorTs,
            @Parameter(description = "上一页返回的 nextCursor.id。", example = "gb32960-frame-xxx")
            @RequestParam(required = false) String cursorId) throws IOException {
        if (vin == null || vin.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 frame page query");
        }
        return service.queryPage(
                QueryTimeRange.parse(dateFrom, dateTo),
                order,
                limit,
                vin,
                platformAccount,
                cursor(cursorTs, cursorId));
    }

    @GetMapping("/frame")
    @Operation(
            summary = "按 rawArchiveUri 查询单个 GB32960 原始帧",
            description = "直接读取指定 archive://*.bin 原始包并返回解析结果。主要用于从 snapshots 的 sourceFrames 回查某个原始报文。")
    public Gb32960DecodedFrameService.DecodedFrame frame(
            @Parameter(description = "原始包归档地址。", example = "archive://2026/06/23/GB32960/LB9A32A20P0LS1257/1782182749928000.bin")
            @RequestParam String rawArchiveUri,
            @Parameter(description = "平台账号；用于选择对应厂商扩展解析规则。", example = "Hyundai")
            @RequestParam(required = false) String platformAccount) throws IOException {
        return service.findByRawArchiveUri(rawArchiveUri, platformAccount);
    }

    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());
    }

    @GetMapping("/snapshots")
    @Operation(
            summary = "查询 GB32960 完整业务快照",
            description = "按 VIN 和时间范围查询完整业务快照。会把同一车辆、同一 eventTime 的多个子包合并为一个 snapshot，适合车辆详情页和某一时刻全字段查看。VIN 为必填。")
    public List<Gb32960DecodedFrameService.LogicalSnapshot> snapshots(
            @Parameter(description = "开始时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:48:00")
            @RequestParam String dateFrom,
            @Parameter(description = "结束时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:49:00")
            @RequestParam String dateTo,
            @Parameter(description = "排序方向。", example = "DESC")
            @RequestParam(defaultValue = "DESC") HistoryQueryOrder order,
            @Parameter(description = "返回 snapshot 数量上限。", example = "10")
            @RequestParam(defaultValue = "10") int limit,
            @Parameter(description = "车辆 VIN，必填；snapshot 查询只支持单车，避免跨车合并和低效扫描。", required = true, example = "LB9A32A20P0LS1257")
            @RequestParam String vin,
            @Parameter(description = "平台账号；用于选择对应厂商扩展解析规则。", example = "Hyundai")
            @RequestParam(required = false) String platformAccount) throws IOException {
        // snapshot 是按同一 VIN + eventTime 合并多个 RAW 子包，跨车查询会造成错误合并和低效扫描。
        if (vin == null || vin.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 snapshots");
        }
        return service.snapshots(QueryTimeRange.parse(dateFrom, dateTo), order, limit, vin, platformAccount);
    }

    @GetMapping("/snapshots/fields")
    @Operation(
            summary = "按所需字段查询 GB32960 快照",
            description = "高频业务查询接口。按 VIN、时间范围和字段列表返回轻量快照，只包含 fields 指定字段；字段格式为 数据包代码.字段名，例如 VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV。字段名应来自 dictionary 接口。VIN 和 fields 为必填。")
    public List<Gb32960DecodedFrameService.FieldSnapshot> snapshotFields(
            @Parameter(description = "开始时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:48:00")
            @RequestParam String dateFrom,
            @Parameter(description = "结束时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:49:00")
            @RequestParam String dateTo,
            @Parameter(description = "排序方向。", example = "DESC")
            @RequestParam(defaultValue = "DESC") HistoryQueryOrder order,
            @Parameter(description = "返回 snapshot 数量上限。", example = "10")
            @RequestParam(defaultValue = "10") int limit,
            @Parameter(description = "车辆 VIN，必填；字段查询只支持单车。", required = true, example = "LB9A32A20P0LS1257")
            @RequestParam String vin,
            @Parameter(description = "逗号分隔的字段列表，字段来自 dictionary；格式为 数据包代码.字段名。", required = true, example = "VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV,GD_FC_DCDC.outputCurrentA")
            @RequestParam String fields,
            @Parameter(description = "平台账号；用于选择对应厂商扩展解析规则。", example = "Hyundai")
            @RequestParam(required = false) String platformAccount) throws IOException {
        // 字段投影接口是前端高频接口，强制单车查询可以命中 VIN 分区，避免全库扫描后再 join。
        if (vin == null || vin.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 snapshot field query");
        }
        return service.snapshotFields(QueryTimeRange.parse(dateFrom, dateTo), order, limit, vin, platformAccount, fields);
    }

    @GetMapping(value = "/snapshots/fields.csv", produces = "text/csv;charset=UTF-8")
    @Operation(
            summary = "导出 GB32960 指定字段快照 CSV",
            description = "参数与 snapshots/fields 一致，返回 CSV 文本。基础列和字段列表表头均为中文，字段表头来自 dictionary，例如 整车数据-累计里程(km)。适合前端下载和临时报表。")
    public String snapshotFieldsCsv(
            @Parameter(description = "开始时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:48:00")
            @RequestParam String dateFrom,
            @Parameter(description = "结束时间，支持日期或精确到秒的时间；无时区时按东八区解析。", example = "2026-06-23T10:49:00")
            @RequestParam String dateTo,
            @Parameter(description = "排序方向。", example = "DESC")
            @RequestParam(defaultValue = "DESC") HistoryQueryOrder order,
            @Parameter(description = "返回 snapshot 数量上限。", example = "1000")
            @RequestParam(defaultValue = "1000") int limit,
            @Parameter(description = "车辆 VIN，必填；字段导出只支持单车。", required = true, example = "LB9A32A20P0LS1257")
            @RequestParam String vin,
            @Parameter(description = "逗号分隔的字段列表，字段来自 dictionary；格式为 数据包代码.字段名。", required = true, example = "VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV,GD_FC_DCDC.outputCurrentA")
            @RequestParam String fields,
            @Parameter(description = "平台账号；用于选择对应厂商扩展解析规则。", example = "Hyundai")
            @RequestParam(required = false) String platformAccount) throws IOException {
        // CSV 复用字段投影逻辑，表头中文化由 dictionary 提供，避免导出和页面展示出现两套字段定义。
        if (vin == null || vin.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 snapshot field csv export");
        }
        return service.snapshotFieldsCsv(QueryTimeRange.parse(dateFrom, dateTo), order, limit, vin, platformAccount, fields);
    }

    @GetMapping("/dictionary")
    @Operation(
            summary = "查询 GB32960 字段字典",
            description = "返回数据包代码、数据包中文名、字段 key、中文名、单位、解释和枚举/位图值域对照。前端应用可用它渲染字段选择器、表头、Tooltip 和状态码中文展示。")
    public List<Gb32960FieldDictionary.Packet> dictionary() {
        return Gb32960FieldDictionary.packets();
    }
}
