package kr.co.accura.wbx.spring.file;

import kr.co.accura.wbx.spring.common.BusinessException;
import kr.co.accura.wbx.spring.config.WbxSpringProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.UUID;

/**
 * Azure Blob Storage 구현
 * <p>
 * Azure SDK 없이 REST API 직접 호출 (의존성 최소화).
 * 고객사에 Azure SDK 사용 가능하면 spring-cloud-azure-starter-storage-blob으로 교체 권장.
 * <p>
 * 설정:
 * <pre>
 * wbx.spring.file.storage-type: azure-blob
 * wbx.spring.file.azure.account-name: ${AZURE_STORAGE_ACCOUNT}
 * wbx.spring.file.azure.account-key: ${AZURE_STORAGE_KEY}
 * wbx.spring.file.azure.container-name: uploads
 * </pre>
 */
@Slf4j
@Service
@ConditionalOnProperty(name = "wbx.spring.file.storage-type", havingValue = "azure-blob")
public class AzureBlobStorageService implements FileStorageService {

    private final WbxSpringProperties props;
    private final HttpClient httpClient;

    public AzureBlobStorageService(WbxSpringProperties props) {
        this.props = props;
        this.httpClient = HttpClient.newHttpClient();
    }

    @Override
    public String upload(MultipartFile file, String category) {
        try {
            String blobName = category + "/" + UUID.randomUUID() + getExtension(file.getOriginalFilename());
            // SAS Token 방식 — SharedKey 서명 없이 URL에 인증 포함
            String sasUrl = generateSasUrl(blobName, "rcw", 3600); // read+create+write, 1시간

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(sasUrl))
                    .header("x-ms-blob-type", "BlockBlob")
                    .header("Content-Type", file.getContentType() != null ? file.getContentType() : "application/octet-stream")
                    .header("x-ms-version", "2023-11-03")
                    .PUT(HttpRequest.BodyPublishers.ofByteArray(file.getBytes()))
                    .build();

            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() >= 300) {
                throw new BusinessException("Azure Blob upload failed: HTTP " + response.statusCode() + " " + response.body());
            }

            log.info("Azure Blob uploaded: {}", blobName);
            return blobName;
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            throw new BusinessException("Azure Blob upload failed: " + e.getMessage());
        }
    }

    @Override
    public byte[] download(String fileKey) {
        try {
            String sasUrl = generateSasUrl(fileKey, "r", 3600); // read, 1시간
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(sasUrl))
                    .header("x-ms-version", "2023-11-03")
                    .GET()
                    .build();

            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            if (response.statusCode() == 404) {
                throw new BusinessException("File not found: " + fileKey);
            }
            return response.body();
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            throw new BusinessException("Azure Blob download failed: " + e.getMessage());
        }
    }

    @Override
    public void delete(String fileKey) {
        try {
            String sasUrl = generateSasUrl(fileKey, "d", 3600); // delete, 1시간
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(sasUrl))
                    .header("x-ms-version", "2023-11-03")
                    .DELETE()
                    .build();
            httpClient.send(request, HttpResponse.BodyHandlers.ofString());
            log.info("Azure Blob deleted: {}", fileKey);
        } catch (Exception e) {
            log.warn("Azure Blob delete failed: {}", e.getMessage());
        }
    }

    @Override
    public String getPresignedUrl(String fileKey, long expirySeconds) {
        return generateSasUrl(fileKey, "r", expirySeconds);
    }

    /**
     * Azure SAS Token URL 생성 (Service SAS)
     * @param blobName Blob 경로
     * @param permissions 권한: r=read, w=write, d=delete, c=create 조합
     * @param expirySeconds 유효기간 (초)
     */
    private String generateSasUrl(String blobName, String permissions, long expirySeconds) {
        WbxSpringProperties.FileConfig.AzureConfig azure = props.getFile().getAzure();
        String accountName = azure.getAccountName();
        String containerName = azure.getContainerName();

        OffsetDateTime start = OffsetDateTime.now(ZoneOffset.UTC).minusMinutes(5); // 시간 오차 대비
        OffsetDateTime expiry = OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(expirySeconds);
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");

        String stringToSign = String.join("\n",
                permissions,
                fmt.format(start),
                fmt.format(expiry),
                String.format("/blob/%s/%s/%s", accountName, containerName, blobName),
                "", // identifier
                "", // IP
                "https", // protocol
                "2023-11-03", // version
                "b", // resource (blob)
                "", "", "", "" // snapshot, encryption, cache, content
        );

        String signature = hmacSha256(azure.getAccountKey(), stringToSign);
        return String.format("%s?sp=%s&st=%s&se=%s&spr=https&sv=2023-11-03&sr=b&sig=%s",
                getBlobUrl(blobName), permissions, fmt.format(start), fmt.format(expiry),
                java.net.URLEncoder.encode(signature, java.nio.charset.StandardCharsets.UTF_8));
    }

    // ===== Helper =====

    private String getBlobUrl(String blobName) {
        WbxSpringProperties.FileConfig.AzureConfig azure = props.getFile().getAzure();
        return String.format("https://%s.blob.core.windows.net/%s/%s",
                azure.getAccountName(), azure.getContainerName(), blobName);
    }

    private String gmtDate() {
        return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
    }

    private String hmacSha256(String key, String data) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(key);
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(keyBytes, "HmacSHA256"));
            return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
        } catch (Exception e) {
            throw new BusinessException("HMAC signing failed");
        }
    }

    private String getExtension(String filename) {
        if (filename == null) return "";
        int dot = filename.lastIndexOf('.');
        return dot > 0 ? "." + filename.substring(dot + 1).toLowerCase() : "";
    }
}
