package com.nanri.aiimage.modules.productrisk.controller;

import com.nanri.aiimage.common.api.ApiResponse;
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskCandidateAddRequest;
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskCountryPreferenceSaveRequest;
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskCreateTaskRequest;
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskMatchShopsRequest;
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskSubmitResultRequest;
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskTaskBatchRequest;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskCandidateVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskCountryPreferenceVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskCreateTaskVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskDashboardVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskHistoryVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskMatchShopsVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskPendingDeleteVo;
import com.nanri.aiimage.modules.productrisk.model.vo.ProductRiskTaskBatchVo;
import com.nanri.aiimage.modules.productrisk.service.ProductRiskResolveService;
import com.nanri.aiimage.modules.productrisk.service.ProductRiskTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
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 org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import java.io.InputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/product-risk-resolve")
@Tag(
        name = "商品风险解决",
        description = "亚马逊商品风险处理：备选店铺、紫鸟匹配、创建任务、Python 队列与回传、生成 zip。"
                + "查询类接口需带 Query 参数 user_id（与登录用户一致）。")
public class ProductRiskResolveController {

    private final ProductRiskResolveService productRiskResolveService;
    private final ProductRiskTaskService productRiskTaskService;

    @GetMapping("/candidates")
    @Operation(
            summary = "查询备选店铺列表",
            description = "返回当前用户在「商品风险」模块中已保存的备选店铺，按创建时间倒序。用于前端勾选后再调用匹配接口。")
    public ApiResponse<List<ProductRiskCandidateVo>> listCandidates(
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID，仅返回该用户下的备选店铺",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        return ApiResponse.success(productRiskResolveService.listCandidates(userId));
    }

    @PostMapping("/candidates")
    @Operation(
            summary = "新增备选店铺",
            description = "将店铺名写入用户备选表；店铺名会规范化去重。仅用于前端勾选与匹配流程，与任务/结果表无强绑定。")
    public ApiResponse<ProductRiskCandidateVo> addCandidate(@Valid @RequestBody ProductRiskCandidateAddRequest request) {
        return ApiResponse.success(productRiskResolveService.addCandidate(request));
    }

    @DeleteMapping("/candidates/{id}")
    @Operation(
            summary = "删除备选店铺",
            description = "按主键删除一条备选记录；`id` 必须属于当前 `user_id`，否则报错。")
    public ApiResponse<Void> deleteCandidate(
            @Parameter(description = "备选店铺记录主键（biz_product_risk_shop_candidate.id）", example = "10")
            @PathVariable Long id,
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID，用于校验数据归属",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        productRiskResolveService.deleteCandidate(userId, id);
        return ApiResponse.success(null);
    }

    @GetMapping("/country-preference")
    @Operation(
            summary = "查询用户五国处理顺序",
            description = "返回当前用户已保存的国家代码列表（顺序即处理顺序）；未保存时返回默认：DE、UK、FR、IT、ES（全选）。")
    public ApiResponse<ProductRiskCountryPreferenceVo> getCountryPreference(
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        return ApiResponse.success(productRiskResolveService.getCountryPreference(userId));
    }

    @PutMapping("/country-preference")
    @Operation(
            summary = "保存用户五国处理顺序",
            description = "写入 biz_product_risk_country_pref；country_codes 为 ProductRiskCountryCode 枚举名，至少 1 个、无重复。")
    public ApiResponse<ProductRiskCountryPreferenceVo> saveCountryPreference(
            @Valid @RequestBody ProductRiskCountryPreferenceSaveRequest request) {
        return ApiResponse.success(productRiskResolveService.saveCountryPreference(request));
    }

    @PostMapping("/match-shops")
    @Operation(
            summary = "批量匹配店铺（紫鸟索引）",
            description = "按店名查紫鸟索引，返回 shopId、platform、companyName、matchStatus 等；items 可作队列 data.items。"
                    + "未命中时 matched=false，不可创建任务。")
    public ApiResponse<ProductRiskMatchShopsVo> matchShops(@Valid @RequestBody ProductRiskMatchShopsRequest request) {
        return ApiResponse.success(productRiskResolveService.matchShops(request));
    }

    @GetMapping("/dashboard")
    @Operation(
            summary = "统计看板",
            description = "汇总当前用户：备选店铺数量；商品风险模块下已结束任务数（SUCCESS/FAILED）；其中成功任务数、失败任务数。")
    public ApiResponse<ProductRiskDashboardVo> dashboard(
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        return ApiResponse.success(productRiskTaskService.dashboard(userId));
    }

    @GetMapping("/history")
    @Operation(
            summary = "处理记录列表（含进行中与已结束）",
            description = "返回当前用户商品风险模块下各店结果行（biz_file_result），按创建时间倒序最多 100 条；"
                    + "含 resultId、taskId、店名、任务状态、success、错误、输出文件名等。")
    public ApiResponse<ProductRiskHistoryVo> history(
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        return ApiResponse.success(productRiskTaskService.listHistory(userId));
    }

    @DeleteMapping("/history/{resultId}")
    @Operation(
            summary = "删除单条店铺结果记录",
            description = "删除一条 biz_file_result（须为本模块且归属 user_id）；随后重算父任务，无子结果则删任务。")
    public ApiResponse<Void> deleteHistory(
            @Parameter(description = "结果行主键 biz_file_result.id", example = "1001")
            @PathVariable Long resultId,
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID，用于校验该结果归属",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        productRiskTaskService.deleteHistory(resultId, userId);
        return ApiResponse.success(null);
    }

    @PostMapping("/tasks")
    @Operation(
            summary = "创建商品风险处理任务",
            description = "推 Python 队列前调用：落库 RUNNING 任务及各店 biz_file_result；响应 taskId 写入队列 data.taskId，"
                    + "items 含各店 resultId。请求 items 须全为 matched=true，店名会去重。")
    public ApiResponse<ProductRiskCreateTaskVo> createTask(@Valid @RequestBody ProductRiskCreateTaskRequest request) {
        return ApiResponse.success(productRiskTaskService.createTask(request));
    }

    @DeleteMapping("/tasks/{taskId}")
    @Operation(
            summary = "删除整条任务",
            description = "删除该任务及下属全部结果行（RUNNING/SUCCESS/FAILED 均可）；user_id 须与任务归属一致。")
    public ApiResponse<Void> deleteTask(
            @Parameter(description = "任务主键 biz_file_task.id", example = "200")
            @PathVariable Long taskId,
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID，须与任务归属一致",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId) {
        productRiskTaskService.deleteTask(taskId, userId);
        return ApiResponse.success(null);
    }

    @DeleteMapping("/pending-shop-result")
    @Operation(
            summary = "按店铺名删除运行中任务下的一条结果",
            description = "仅在 RUNNING 任务下按规范化店名删一条 biz_file_result 并重算任务；无匹配时 removed=false。"
                    + "用于前端从匹配列表移除店铺时同步后端占位。")
    public ApiResponse<ProductRiskPendingDeleteVo> deletePendingShopResult(
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId,
            @Parameter(
                    name = "shop_name",
                    description = "店铺名称（与匹配/创建任务时一致；服务端会按紫鸟规则规范化后再与库中 source_filename 比对）",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "某某店铺")
            @RequestParam("shop_name") String shopName) {
        return ApiResponse.success(productRiskTaskService.deletePendingShopResult(userId, shopName));
    }

    @PostMapping("/tasks/batch")
    @Operation(
            summary = "批量查询任务详情（轮询）",
            description = "按 taskIds 返回任务概要及各店结果；无效 id 记入 missingTaskIds。供前端轮询 RUNNING 至结束。")
    public ApiResponse<ProductRiskTaskBatchVo> tasksBatch(@Valid @RequestBody ProductRiskTaskBatchRequest request) {
        return ApiResponse.success(productRiskTaskService.getTaskDetailsBatch(request.getTaskIds()));
    }

    @PostMapping("/tasks/{taskId}/result")
    @Operation(
            summary = "Python 回传处理结果（可多次、可分批）",
            description = "Python 调用；同一 taskId 可多次，未出现在 shops 中的店保持待处理。"
                    + "有 error 则标店失败；否则按 countries（键为五国枚举 DE/FR/ES/IT/UK）生成 xlsx/zip 上传 OSS，"
                    + "Excel 内仍为中文工作表名。全部店终态后任务 SUCCESS/FAILED。")
    public ApiResponse<Void> submitResult(
            @Parameter(description = "任务主键，须为 RUNNING 状态", example = "200")
            @PathVariable Long taskId,
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    description = "按店铺回传：成功时 `countries` 的 key 为枚举 ProductRiskCountryCode（JSON 与枚举名一致：DE、FR、ES、IT、UK）；"
                            + "失败时填 `error`，可不传 `countries`",
                    required = true,
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ProductRiskSubmitResultRequest.class),
                            examples = {
                                    @ExampleObject(
                                            name = "成功（单店，五国含样例行）",
                                            summary = "DE、UK 各一行，其余国可为空数组",
                                            value = """
                                                    {
                                                      "shops": [
                                                        {
                                                          "shopName": "模拟旗舰店A",
                                                          "countries": {
                                                            "DE": [
                                                              {
                                                                "shopName": "模拟旗舰店A",
                                                                "productAsinSku": "B0DEMO1234",
                                                                "status": "风险待解除",
                                                                "done": "否",
                                                                "removeAsin": "B0OLD5678",
                                                                "removeStatus": "排队中"
                                                              }
                                                            ],
                                                            "FR": [],
                                                            "ES": [],
                                                            "IT": [],
                                                            "UK": [
                                                              {
                                                                "shopName": "模拟旗舰店A",
                                                                "productAsinSku": "B0UKDEMO99",
                                                                "status": "正常",
                                                                "done": "是",
                                                                "removeAsin": "",
                                                                "removeStatus": ""
                                                              }
                                                            ]
                                                          }
                                                        }
                                                      ]
                                                    }
                                                    """),
                                    @ExampleObject(
                                            name = "失败（单店仅 error）",
                                            summary = "该店标记失败，不生成 zip",
                                            value = """
                                                    {
                                                      "shops": [
                                                        {
                                                          "shopName": "模拟旗舰店B",
                                                          "error": "紫鸟会话超时，未能导出表格"
                                                        }
                                                      ]
                                                    }
                                                    """)
                            }))
            @Valid @RequestBody ProductRiskSubmitResultRequest request) {
        productRiskTaskService.submitResult(taskId, request);
        return ApiResponse.success(null);
    }

    @GetMapping("/results/{resultId}/download")
    @Operation(
            summary = "流式下载单个店铺的 zip 结果",
            description = "从 OSS 拉取 zip 以附件写入响应体（非 JSON）；须归属 user_id；无文件则 404。")
    public void downloadResult(
            @Parameter(description = "店铺结果行主键 biz_file_result.id", example = "1001")
            @PathVariable Long resultId,
            @Parameter(
                    name = "user_id",
                    description = "当前用户 ID",
                    required = true,
                    in = ParameterIn.QUERY,
                    example = "1")
            @RequestParam("user_id") Long userId,
            jakarta.servlet.http.HttpServletResponse response) {
        String url = productRiskTaskService.resolveResultDownloadUrl(resultId, userId);
        String filename = productRiskTaskService.resolveResultDownloadFilename(resultId, userId);
        if (url == null || url.isBlank()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "暂无可下载结果");
        }
        try {
            String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
            response.setContentType("application/octet-stream");
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + filename + "\"; filename*=UTF-8''" + encodedFilename);
            try (InputStream in = URI.create(url).toURL().openStream()) {
                byte[] buffer = new byte[65536];
                int read;
                while ((read = in.read(buffer)) != -1) {
                    response.getOutputStream().write(buffer, 0, read);
                }
                response.getOutputStream().flush();
            }
        } catch (Exception ex) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "下载失败");
        }
    }
}
