package com.rdkr.tide_display.backend.gcp.service;

import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.Storage.BlobListOption;
import com.rdkr.tide_display.backend.gcp.Firmware;
import com.rdkr.tide_display.backend.gcp.StorageService;
import jakarta.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
public class StorageServiceImpl implements StorageService {

  final Storage storage;
  final String bucketName = "ec804bd8-38d2-4fcc-8f04-5addb55b3c90";

  private String extract(byte[] bytes, int start, int length) {
    val result = new StringBuilder();
    for (var pos = start; pos < Math.min(start + length, bytes.length); pos++) {
      result.append((char) bytes[pos]);
    }
    return result.toString().trim();
  }

  @Override
  public List<Firmware> getFirmwareVersions() {
    val result = new ArrayList<Firmware>();
    val bucket = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix("firmware/"));
    for (val directory : bucket.iterateAll()) {
      val files = storage.list(bucketName, BlobListOption.currentDirectory(),
          BlobListOption.prefix(directory.getName()));
      for (val file : files.iterateAll()) {
        val name = file.getName();
        if (name.endsWith(".bin")) {
          val bytes = file.getContent();
          val version = extract(bytes, 48, 30).split("\\.");
          if (version.length == 3) {
            int major = Integer.parseInt(version[0]);
            int minor = Integer.parseInt(version[1]);
            int patch = Integer.parseInt(version[2]);

            val projectName = extract(bytes, 80, 30);
            val flashSizeCode = bytes[3] >> 4;
            val flashSize = (int) Math.pow(2, flashSizeCode);
            val espIdf = extract(bytes, 144, 30);
            val compiled = extract(bytes, 128, 15) + " " + extract(bytes, 112, 15);
            val meta = new Firmware.FirmwareMeta(projectName, flashSize, espIdf, compiled);

            result.add(
                new Firmware(major, minor, patch, "/files/" + major + "." + minor + "." + patch + "/firmware.bin",
                    file.getEtag(), file.getUpdateTimeOffsetDateTime(), meta));
          }
        }
      }
    }
    result.sort((a, b) -> {
      if (a.getMajor() != b.getMajor()) {
        return a.getMajor() - b.getMajor();
      }
      if (a.getMinor() != b.getMinor()) {
        return a.getMinor() - b.getMinor();
      }
      return a.getPatch() - b.getPatch();
    });

    return result.reversed();
  }

  @Override
  public byte[] download(String version, String filename) {
    val blob = storage.get(bucketName, "firmware/" + version + "/" + filename);
    return blob != null ? blob.getContent() : null;
  }

  @Override
  public @Nullable String upload(MultipartFile file) throws IOException {
    val bytes = file.getBytes();
    if (bytes[0] != -23) {
      return null;
    }
    val version = extract(bytes, 48, 30);
    save("firmware/" + version + "/firmware.bin", bytes, MediaType.APPLICATION_OCTET_STREAM_VALUE, 0);
    return version;
  }

  @Override
  public void save(String filename, byte[] data, String contentType, int expire) {
    val metadata = new HashMap<String, String>();
    metadata.put("Cache-Control", "public, max-age=" + expire);
    val blobInfo = BlobInfo
        .newBuilder(bucketName, filename)
        .setContentType(contentType)
        .setMetadata(metadata)
        .build();
    storage.create(blobInfo, data);
  }
}
