package com.red.circle.external.app.oss;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.UUID;

/**
 * 开发环境本地磁盘存储服务.
 */
@Service
public class LocalDiskStorageService {

  private static final Logger log = LoggerFactory.getLogger(LocalDiskStorageService.class);
  private static final String AVATAR_PREFIX = "avatar/";
  private static final String EXTERNAL_LOCAL_PREFIX = "/external/oss/local/";
  private static final String LOCAL_PREFIX = "/oss/local/";

  private final LocalDiskStorageProperties properties;

  public LocalDiskStorageService(LocalDiskStorageProperties properties) {
    this.properties = properties;
  }

  public boolean isEnabled() {
    return properties.isConfigured();
  }

  public String uploadAvatar(InputStream inputStream, String contentType, String suffix) {
    String key = AVATAR_PREFIX + UUID.randomUUID() + normalizeSuffix(suffix);
    writeFile(key, inputStream);
    return buildAccessUrl(key);
  }

  public String upload(String key, InputStream inputStream) {
    writeFile(key, inputStream);
    return buildAccessUrl(key);
  }

  public StoredFile load(String keyOrUrl) {
    String key = resolveKey(keyOrUrl);
    if (!StringUtils.hasText(key)) {
      throw new IllegalArgumentException("local disk key is empty");
    }
    Path path = resolveStoragePath(key);
    if (!Files.exists(path) || !Files.isRegularFile(path)) {
      throw new IllegalArgumentException("local disk file does not exist: " + key);
    }
    try {
      Resource resource = new UrlResource(path.toUri());
      String contentType = Files.probeContentType(path);
      long contentLength = Files.size(path);
      return new StoredFile(
          resource,
          StringUtils.hasText(contentType) ? contentType : "application/octet-stream",
          contentLength);
    } catch (IOException e) {
      throw new IllegalStateException("load local file failed: " + key, e);
    }
  }

  public boolean isManagedUrl(String source) {
    if (!StringUtils.hasText(source)) {
      return false;
    }
    if (!looksLikeUrl(source)) {
      return false;
    }
    String path = URI.create(source).getPath();
    return StringUtils.hasText(path)
        && (path.startsWith(EXTERNAL_LOCAL_PREFIX) || path.startsWith(LOCAL_PREFIX));
  }

  public String getAccessUrl(String keyOrUrl) {
    if (!StringUtils.hasText(keyOrUrl)) {
      return keyOrUrl;
    }
    if (looksLikeUrl(keyOrUrl)) {
      return keyOrUrl;
    }
    return buildAccessUrl(keyOrUrl);
  }

  public void remove(String url) {
    String key = resolveKey(url);
    if (!StringUtils.hasText(key)) {
      return;
    }
    try {
      Files.deleteIfExists(resolveStoragePath(key));
    } catch (IOException e) {
      log.error("remove local file failed url={}", url, e);
      throw new IllegalStateException("remove local file failed", e);
    }
  }

  public void processFileSaveAs(String source, String target) {
    if (!StringUtils.hasText(target)) {
      return;
    }
    copy(source, target);
  }

  public String processImgSaveAsCompressZoom(String source, String target, int height) {
    if (!StringUtils.hasText(target)) {
      return processImgSaveAsCompressZoom(source, height);
    }
    copy(source, target);
    return buildAccessUrl(target);
  }

  public String processImgSaveAsCompressZoom(String source, int height) {
    return getAccessUrl(source);
  }

  private void writeFile(String key, InputStream inputStream) {
    Path target = resolveStoragePath(key);
    try {
      Files.createDirectories(target.getParent());
      Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
      log.error("write local file failed key={}", key, e);
      throw new IllegalStateException("write local file failed", e);
    }
  }

  private void copy(String source, String target) {
    String sourceKey = resolveKey(source);
    if (!StringUtils.hasText(sourceKey)) {
      return;
    }
    Path sourcePath = resolveStoragePath(sourceKey);
    Path targetPath = resolveStoragePath(target);
    try {
      Files.createDirectories(targetPath.getParent());
      Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
      log.error("copy local file failed source={} target={}", source, target, e);
      throw new IllegalStateException("copy local file failed", e);
    }
  }

  private Path resolveStoragePath(String key) {
    Path basePath = Path.of(properties.getBasePath()).toAbsolutePath().normalize();
    Path resolved = basePath.resolve(key).normalize();
    if (!resolved.startsWith(basePath)) {
      throw new IllegalArgumentException("invalid local disk key: " + key);
    }
    return resolved;
  }

  private String resolveKey(String keyOrUrl) {
    if (!StringUtils.hasText(keyOrUrl)) {
      return keyOrUrl;
    }
    if (!looksLikeUrl(keyOrUrl)) {
      return normalizeKey(keyOrUrl);
    }
    String path = URI.create(keyOrUrl).getPath();
    if (!StringUtils.hasText(path)) {
      return null;
    }
    if (path.startsWith(EXTERNAL_LOCAL_PREFIX)) {
      return normalizeKey(path.substring(EXTERNAL_LOCAL_PREFIX.length()));
    }
    if (path.startsWith(LOCAL_PREFIX)) {
      return normalizeKey(path.substring(LOCAL_PREFIX.length()));
    }
    return null;
  }

  private String buildAccessUrl(String key) {
    String accessUrl = properties.getAccessUrl();
    return accessUrl.endsWith("/") ? accessUrl + key : accessUrl + "/" + key;
  }

  private String normalizeKey(String key) {
    return key.startsWith("/") ? key.substring(1) : key;
  }

  private String normalizeSuffix(String suffix) {
    if (!StringUtils.hasText(suffix)) {
      return "";
    }
    return suffix.startsWith(".") ? suffix : "." + suffix;
  }

  private boolean looksLikeUrl(String value) {
    return value.startsWith("http://") || value.startsWith("https://");
  }

  public record StoredFile(Resource resource, String contentType, long contentLength) {
  }
}
