package com.xyw.console.asset.service;

import com.xyw.console.asset.exception.DeviceAssetValidationException;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import javax.imageio.ImageIO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

/** 文件用途（白话）：校验并保存设备原图，且只允许用不透明标识在受控目录中读取图片。 */
@Service
public class DeviceAssetFileStorageService {
  private static final long MAX_IMAGE_BYTES = 20L * 1024 * 1024;
  private static final Set<String> EXTENSIONS = Set.of("jpg", "jpeg", "png", "gif");
  private final Path root;

  /** 代码作用（白话）：解析并创建设备图片根目录。关联文件：DeviceAssetService.java、DeviceAssetController.java。关联逻辑（调用链/数据流）：配置/默认目录 -> 文件服务 -> 保存和读取图片。 */
  public DeviceAssetFileStorageService(@Value("${xyw.device-assets.upload-dir:./uploads/device-assets}") String uploadDirectory) {
    try { root = Path.of(uploadDirectory).toAbsolutePath().normalize(); Files.createDirectories(root); }
    catch (IOException exception) { throw new IllegalStateException("设备图片目录无法创建", exception); }
  }

  /** 代码作用（白话）：保存一张已通过校验的原图并返回不透明文件标识。关联文件：DeviceAssetSaveRequest.java、DeviceAssetService.java。关联逻辑（调用链/数据流）：multipart 图片 -> store -> 文件标识 -> as_asset_device 附件列。 */
  public String store(MultipartFile image) {
    if (image == null || image.isEmpty()) return null;
    validateImage(image);
    String identifier = createOpaqueIdentifier(extensionOf(image.getOriginalFilename()));
    Path target = resolveInsideRoot(identifier);
    try (InputStream input = image.getInputStream()) { Files.copy(input, target, StandardCopyOption.REPLACE_EXISTING); return identifier; }
    catch (IOException exception) { throw new DeviceAssetValidationException("设备图片保存失败"); }
  }

  /** 代码作用（白话）：把不透明标识解析为受目录约束的可读取资源。关联文件：DeviceAssetController.java。关联逻辑（调用链/数据流）：图片 URL -> findImage -> resolve -> ResponseEntity 文件响应。 */
  public Resource resolve(String identifier) {
    if (identifier == null || identifier.isBlank()) throw new DeviceAssetValidationException("图片不存在");
    try { Resource resource = new UrlResource(resolveInsideRoot(identifier).toUri()); if (!resource.exists() || !resource.isReadable()) throw new DeviceAssetValidationException("图片不存在"); return resource; }
    catch (IOException exception) { throw new DeviceAssetValidationException("图片读取失败"); }
  }

  /** 代码作用（白话）：保存替换图或保留旧标识，避免编辑未选图时丢失原图。关联文件：DeviceAssetService.java。关联逻辑（调用链/数据流）：编辑表单 -> replace -> 新/旧标识 -> 设备更新。 */
  public String replace(String currentIdentifier, MultipartFile replacement, boolean remove) {
    if (replacement != null && !replacement.isEmpty()) return store(replacement);
    return remove ? null : currentIdentifier;
  }

  /** 代码作用（白话）：清空数据库中的附件引用但保留物理原图，以支持软删除后追溯。关联文件：DeviceAssetService.java。关联逻辑（调用链/数据流）：移除图片标记 -> removeReference -> 数据库字段置空 -> 原图保留。 */
  public String removeReference() { return null; }

  /** 代码作用（白话）：删除本次失败请求新写入的文件，不接收历史附件标识。关联文件：DeviceAssetService.java。关联逻辑（调用链/数据流）：保存失败 -> cleanupNewFile -> 删除临时新图。 */
  public void cleanupNewFile(String identifier) {
    if (identifier == null) return;
    try { Files.deleteIfExists(resolveInsideRoot(identifier)); } catch (IOException ignored) { }
  }

  /** 代码作用（白话）：同时检查文件大小、扩展名和可解码图像内容。关联文件：DeviceAssetSaveRequest.java、DeviceAssetController.java。关联逻辑（调用链/数据流）：浏览器文件 -> validateImage -> 允许保存或返回 400。 */
  private void validateImage(MultipartFile image) {
    if (image.getSize() > MAX_IMAGE_BYTES) throw new DeviceAssetValidationException("每张图片不能超过 20MB");
    String extension = extensionOf(image.getOriginalFilename());
    if (!EXTENSIONS.contains(extension)) throw new DeviceAssetValidationException("仅支持 JPG、PNG、GIF 图片");
    try (InputStream input = image.getInputStream()) { BufferedImage decoded = ImageIO.read(input); if (decoded == null) throw new DeviceAssetValidationException("图片内容无效"); }
    catch (IOException exception) { throw new DeviceAssetValidationException("图片内容无效"); }
  }

  /** 代码作用（白话）：生成不包含原始文件名的随机标识，降低猜测路径风险。关联文件：DeviceAssetController.java。关联逻辑（调用链/数据流）：上传文件 -> 随机标识 -> 受控访问 URL。 */
  private String createOpaqueIdentifier(String extension) { return UUID.randomUUID() + "." + extension; }

  /** 代码作用（白话）：从原始文件名提取允许的小写扩展名。关联文件：DeviceAssetFileStorageService.java。关联逻辑（调用链/数据流）：文件名 -> extensionOf -> 类型校验与目标文件名。 */
  private String extensionOf(String originalFilename) {
    String name = originalFilename == null ? "" : originalFilename.trim(); int dot = name.lastIndexOf('.');
    return dot < 0 ? "" : name.substring(dot + 1).toLowerCase(Locale.ROOT);
  }

  /** 代码作用（白话）：只允许标准随机标识定位根目录内文件，阻止路径穿越。关联文件：DeviceAssetController.java。关联逻辑（调用链/数据流）：URL 文件标识 -> resolveInsideRoot -> 本地资源。 */
  private Path resolveInsideRoot(String identifier) {
    if (!identifier.matches("[0-9a-fA-F-]{36}\\.(jpg|jpeg|png|gif)")) throw new DeviceAssetValidationException("图片标识无效");
    Path resolved = root.resolve(identifier).normalize();
    if (!resolved.startsWith(root)) throw new DeviceAssetValidationException("图片标识无效");
    return resolved;
  }
}
