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

import com.nanri.aiimage.common.api.ApiResponse;
import com.nanri.aiimage.modules.brand.model.dto.BrandCrawlResultRequest;
import com.nanri.aiimage.modules.brand.model.dto.BrandTaskCreateRequest;
import com.nanri.aiimage.modules.brand.model.vo.BrandCrawlPayloadVo;
import com.nanri.aiimage.modules.brand.model.vo.BrandSimpleVo;
import com.nanri.aiimage.modules.brand.model.vo.LegacyBrandTaskDetailVo;
import com.nanri.aiimage.modules.brand.model.vo.LegacyBrandTaskListVo;
import com.nanri.aiimage.modules.brand.service.BrandTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/brand")
@Tag(name = "品牌任务", description = "品牌任务接口。Java 端只负责下载并解析 OSS 文件、返回待爬取数据、接收爬取结果并生成 xlsx/上传 OSS/落旧表，不负责实际爬虫执行。")
public class BrandTaskController {

    private final BrandTaskService brandTaskService;

    @PostMapping("/tasks")
    @Operation(
            summary = "创建品牌任务并返回待爬取数据",
            description = """
                    创建一条品牌任务记录到旧表 brand_crawl_tasks，随后立即下载并读取 Excel，
                    将原始解析结果临时缓存到后端，并把 sheetName、columns、rows、uniqueBrands 等待爬取数据返回给前端。

                    适用流程：
                    1. 前端先拿到 OSS 文件链接；
                    2. 再调用本接口创建任务；
                    3. 前端拿到返回数据后自行执行品牌爬取；
                    4. 爬取结束后调用结果提交接口回传结果。
                    """)
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "创建成功，返回任务 ID 和待爬取数据", content = @Content(schema = @Schema(implementation = BrandCrawlPayloadVo.class))),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "请求参数不合法或文件列表为空"),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "下载或解析 Excel 或创建任务失败")
    })
    public ApiResponse<BrandCrawlPayloadVo> createTask(
            @Parameter(description = "用户 ID", required = true) @RequestParam Long userId,
            @Valid @RequestBody BrandTaskCreateRequest request) {
        return ApiResponse.success(brandTaskService.createTaskAndBuildPayload(userId, request));
    }

    @GetMapping("/tasks")
    @Operation(
            summary = "获取品牌任务列表",
            description = """
                    查询旧表 brand_crawl_tasks 最近的任务列表，返回前端任务面板需要的核心信息。

                    返回内容包括：
                    - id：任务 ID；
                    - file_paths：任务创建时记录的源文件信息；
                    - desc：任务描述，通常由 strategy + 文件名摘要组成；
                    - strategy：品牌匹配方式；
                    - status：任务状态（pending/running/success/failed/cancelled）；
                    - result_paths：结果文件地址信息，通常包含 urls 和 zip_url；
                    - error_message：失败原因；
                    - progress_current / progress_total：当前进度；
                    - created_at / updated_at：创建和更新时间。

                    典型用途：
                    - 页面任务列表展示；
                    - 判断任务是否已完成；
                    - 决定是否显示下载按钮；
                    - 展示失败原因和进度。
                    """
    )
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功，返回任务列表", content = @Content(schema = @Schema(implementation = LegacyBrandTaskListVo.class)))
    })
    public ApiResponse<LegacyBrandTaskListVo> listTasks(
            @Parameter(description = "用户 ID", required = true) @RequestParam Long userId) {
        return ApiResponse.success(brandTaskService.listTasksLegacy(userId));
    }

    @GetMapping("/tasks/{taskId}")
    @Operation(
            summary = "获取品牌任务详情",
            description = """
                    根据任务 ID 查询单条品牌任务详情，返回任务基础信息 + 文件级摘要进度 + Redis 中基于结果分片累计出的实时行级进度。

                    返回内容包括：
                    - task.id：任务 ID；
                    - task.file_paths：任务绑定的源文件信息；
                    - task.desc / task.strategy / task.status：任务描述、策略、状态；
                    - task.result_paths：结果文件地址。单文件场景通常只有 urls，多文件场景通常同时包含 urls 和 zip_url；
                    - task.error_message：失败原因；
                    - task.progress_current / task.progress_total：文件级摘要进度，表示已完成文件数 / 总文件数；
                    - task.created_at / task.updated_at：创建和更新时间；
                    - line_progress.has_progress：当前是否存在 Redis 实时进度；
                    - line_progress.info.file_index / file_total：当前处理到第几个文件 / 总文件数；
                    - line_progress.info.file_name：当前处理文件名；
                    - line_progress.info.current_line / total_lines：当前文件已处理行数 / 总行数；
                    - line_progress.info.phase：当前阶段，可能为 crawling / assembling / uploading / failed。

                    典型用途：
                    - 前端轮询任务状态；
                    - 展示当前文件与当前行进度；
                    - 提交结果后确认任务是否已 success；
                    - 下载前校验任务结果是否已生成；
                    - 任务失败时获取具体错误信息。
                    """
    )
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功，返回单条任务详情", content = @Content(schema = @Schema(implementation = LegacyBrandTaskDetailVo.class))),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "任务不存在")
    })
    public ApiResponse<LegacyBrandTaskDetailVo> getTask(
            @Parameter(description = "品牌任务 ID", required = true) @PathVariable Long taskId) {
        return ApiResponse.success(brandTaskService.getTaskDetailLegacy(taskId));
    }

    @PostMapping("/tasks/{taskId}/result")
    @Operation(
            summary = "提交品牌任务爬取结果",
            description = """
                    前端完成品牌爬取后，将按品牌汇总后的结果分片提交给本接口。
                    Java 端会累计接收前端每次提交的少量结果（例如 5 个品牌），
                    并直接根据 keptRows / invalidBrands / queryFailedBrands 的数量推进 Redis 中的实时进度。
                    在所有文件都提交完成后再基于创建任务时缓存的原始数据统一：
                    - 按“品牌”列删除 invalidBrands 对应的原始行，生成主 sheet；
                    - keptRows 用于统计已处理且保留的品牌进度；
                    - 生成“不符合品牌”和“查询失败品牌” sheet；
                    - 输出 xlsx；
                    - 上传 OSS；
                    - 回写 brand_crawl_tasks.result_paths/status/error_message；

                    处理期间前端可轮询任务详情接口 `GET /api/brand/tasks/{taskId}`，
                    获取 status、progress_current、progress_total、error_message、line_progress 等实时进度信息。
                    """)
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "提交成功，结果文件已生成并落库", content = @Content(schema = @Schema(implementation = BrandSimpleVo.class))),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "请求参数不合法、fileUrl 不匹配或结果数据为空"),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "生成结果文件或上传 OSS 失败")
    })
    public ApiResponse<BrandSimpleVo> submitResult(
            @Parameter(description = "品牌任务 ID", required = true) @PathVariable Long taskId,
            @Valid @RequestBody BrandCrawlResultRequest request) {
        brandTaskService.submitCrawlResult(taskId, request);
        return ApiResponse.success(new BrandSimpleVo(true));
    }

    @PostMapping("/tasks/{taskId}/cancel")
    @Operation(
            summary = "取消品牌任务",
            description = "将任务状态从 pending/running 更新为 cancelled。适用于前端停止本地爬取时同步取消任务记录。"
    )
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "取消成功", content = @Content(schema = @Schema(implementation = BrandSimpleVo.class))),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "任务不存在或当前状态不可取消")
    })
    public ApiResponse<BrandSimpleVo> cancelTask(
            @Parameter(description = "品牌任务 ID", required = true) @PathVariable Long taskId) {
        brandTaskService.cancelTask(taskId);
        return ApiResponse.success(new BrandSimpleVo(true));
    }

    @DeleteMapping("/tasks/{taskId}")
    @Operation(
            summary = "删除品牌任务",
            description = "删除旧表 brand_crawl_tasks 中的任务记录。仅允许删除非 running 状态任务。"
    )
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "删除成功", content = @Content(schema = @Schema(implementation = BrandSimpleVo.class))),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "任务不存在或正在执行中不可删除")
    })
    public ApiResponse<BrandSimpleVo> deleteTask(
            @Parameter(description = "品牌任务 ID", required = true) @PathVariable Long taskId) {
        brandTaskService.deleteTask(taskId);
        return ApiResponse.success(new BrandSimpleVo(true));
    }

    @GetMapping("/tasks/{taskId}/download")
    @Operation(
            summary = "下载品牌任务结果",
            description = "根据任务 ID 读取 brand_crawl_tasks.result_paths，后端代理拉取 OSS 文件并直接返回给浏览器，避免跨域问题。"
    )
    @ApiResponses({
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "文件流（application/zip）"),
            @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "任务不存在或无结果可下载")
    })
    public void download(
            @Parameter(description = "品牌任务 ID", required = true) @PathVariable Long taskId,
            HttpServletResponse response) throws Exception {
        String ossUrl = brandTaskService.resolveDownloadUrl(taskId);
        // 从 OSS URL 路径中取文件名
        String rawPath = URI.create(ossUrl).getPath();
        String filename = rawPath.substring(rawPath.lastIndexOf('/') + 1);
        if (filename.isBlank()) {
            filename = "brand_task_" + taskId + ".zip";
        }
        String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
        response.setContentType("application/zip");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=\"" + filename + "\"; filename*=UTF-8''" + encodedFilename);
        // 后端代理拉取 OSS 文件并流式写出
        try (InputStream in = URI.create(ossUrl).toURL().openStream()) {
            byte[] buffer = new byte[65536];
            int read;
            while ((read = in.read(buffer)) != -1) {
                response.getOutputStream().write(buffer, 0, read);
            }
            response.getOutputStream().flush();
        }
    }
}
