package cn.yunzhixue.ability.center.examsprint.infrastructure.report.storage;

import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportStorage;
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobContainerClientBuilder;
import com.azure.storage.blob.models.BlobHttpHeaders;
import com.azure.storage.common.StorageSharedKeyCredential;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;

@Component
@ConditionalOnProperty(prefix = "ability.exam-sprint.report.storage", name = "type", havingValue = "azure")
public class AzureBlobExamSprintReportStorage implements ExamSprintReportStorage {

    private final BlobContainerClient containerClient;
    private final String containerName;
    private final String downloadUrlPrefix;
    private final Clock clock;

    @Autowired
    public AzureBlobExamSprintReportStorage(
            @Value("${ability.exam-sprint.report.storage.container-name:exam-sprint-reports}") String containerName,
            @Value("${ability.exam-sprint.report.storage.connection-string:}") String connectionString,
            @Value("${ability.exam-sprint.report.storage.endpoint:}") String endpoint,
            @Value("${ability.exam-sprint.report.storage.account-name:}") String accountName,
            @Value("${ability.exam-sprint.report.storage.account-key:}") String accountKey,
            @Value("${ability.exam-sprint.report.storage.download-url-prefix:}") String downloadUrlPrefix,
            Clock clock) {
        this(buildContainerClient(containerName, connectionString, endpoint, accountName, accountKey),
                containerName,
                downloadUrlPrefix,
                clock);
        this.containerClient.createIfNotExists();
    }

    AzureBlobExamSprintReportStorage(BlobContainerClient containerClient, String containerName, String downloadUrlPrefix, Clock clock) {
        this.containerClient = containerClient;
        this.containerName = normalizeSegment(containerName, "containerName");
        this.downloadUrlPrefix = normalizeDownloadUrlPrefix(downloadUrlPrefix);
        this.clock = clock;
    }

    @Override
    public StoredExamSprintReportFile upload(
            String reportId,
            ReportType reportType,
            String fileName,
            byte[] pdfBytes,
            Instant expiresAt) {
        String blobName = fileName;
        BlobClient blobClient = containerClient.getBlobClient(blobName);
        blobClient.upload(new ByteArrayInputStream(pdfBytes), pdfBytes.length, true);
        blobClient.setHttpHeaders(new BlobHttpHeaders().setContentType("application/pdf"));
        blobClient.setMetadata(Map.of(
                "reportId", reportId,
                "reportType", reportType.name(),
                "expiresAt", expiresAt.toString(),
                "uploadedAt", clock.instant().toString()));
        return new StoredExamSprintReportFile(blobName, fileName);
    }

    @Override
    public URI generateDownloadUrl(String storageObjectKey, Duration ttl) {
        // This implementation returns a public Blob URL, so ttl is not used.
        String normalizedStorageObjectKey = normalizeSegment(storageObjectKey, "storageObjectKey");
        return URI.create(downloadUrlPrefix + "/" + containerName + "/" + normalizedStorageObjectKey);
    }

    @Override
    public Optional<StoredExamSprintReportContent> download(String storageObjectKey) {
        BlobClient blobClient = containerClient.getBlobClient(storageObjectKey);
        if (!blobClient.exists()) {
            return Optional.empty();
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        blobClient.downloadStream(outputStream);
        String contentType = blobClient.getProperties().getContentType();
        return Optional.of(new StoredExamSprintReportContent(
                fileName(storageObjectKey),
                outputStream.toByteArray(),
                contentType == null || contentType.isBlank() ? "application/pdf" : contentType));
    }

    @Override
    public void delete(String storageObjectKey) {
        containerClient.getBlobClient(storageObjectKey).deleteIfExists();
    }

    private String fileName(String storageObjectKey) {
        return storageObjectKey.substring(storageObjectKey.lastIndexOf('/') + 1);
    }

    private static String normalizeDownloadUrlPrefix(String value) {
        if (!hasText(value)) {
            throw new IllegalStateException("Azure storage download-url-prefix is incomplete");
        }
        String normalized = value.trim();
        while (normalized.endsWith("/")) {
            normalized = normalized.substring(0, normalized.length() - 1);
        }
        if (!hasText(normalized)) {
            throw new IllegalStateException("Azure storage download-url-prefix is incomplete");
        }
        return normalized;
    }

    private static String normalizeSegment(String value, String name) {
        String normalized = trimSlashes(value);
        if (!hasText(normalized)) {
            throw new IllegalStateException("Azure storage " + name + " is incomplete");
        }
        return normalized;
    }

    private static String trimSlashes(String value) {
        if (value == null) {
            return "";
        }
        String normalized = value.trim();
        while (normalized.startsWith("/")) {
            normalized = normalized.substring(1);
        }
        while (normalized.endsWith("/")) {
            normalized = normalized.substring(0, normalized.length() - 1);
        }
        return normalized;
    }

    private static BlobContainerClient buildContainerClient(
            String containerName,
            String connectionString,
            String endpoint,
            String accountName,
            String accountKey) {
        String normalizedContainerName = normalizeSegment(containerName, "containerName");
        BlobContainerClientBuilder builder = new BlobContainerClientBuilder().containerName(normalizedContainerName);

        if (hasText(connectionString)) {
            builder.connectionString(connectionString);
        } else if (hasText(endpoint) && hasText(accountName) && hasText(accountKey)) {
            builder.endpoint(endpoint)
                    .credential(new StorageSharedKeyCredential(accountName, accountKey));
        } else {
            throw new IllegalStateException("Azure storage configuration is incomplete");
        }

        return builder.buildClient();
    }

    private static boolean hasText(String value) {
        return value != null && !value.isBlank();
    }
}
