<?php

namespace App\Services;

use Illuminate\Support\Facades\Log;

/**
 * Google Cloud Storage 업로드 서비스
 *
 * 우선순위:
 * 1. DB 설정 (ui에서 오버라이드)
 * 2. 환경변수 (.env)
 * 3. 레거시 파일 (/sales/apikey/)
 *
 * JWT 인증 방식 사용.
 */
class GoogleCloudStorageService
{
    private ?string $bucketName = null;

    private ?array $serviceAccount = null;

    private string $configSource = 'none';

    public function __construct()
    {
        $this->loadConfig();
    }

    /**
     * GCS 설정 로드 (.env 기반)
     */
    private function loadConfig(): void
    {
        $this->bucketName = config('services.google.storage_bucket');
        $credentialsPath = config('services.google.credentials_path');

        if ($credentialsPath && file_exists($credentialsPath)) {
            $this->serviceAccount = json_decode(file_get_contents($credentialsPath), true);
        }

        // fallback: 레거시 파일 경로
        if (! $this->serviceAccount) {
            $legacyPath = '/var/www/sales/apikey/google_service_account.json';
            if (file_exists($legacyPath)) {
                $this->serviceAccount = json_decode(file_get_contents($legacyPath), true);
            }
        }

        if ($this->bucketName && $this->serviceAccount) {
            $this->configSource = 'env';
        }
    }

    /**
     * 현재 설정 소스 반환
     */
    public function getConfigSource(): string
    {
        return $this->configSource;
    }

    /**
     * GCS가 사용 가능한지 확인
     */
    public function isAvailable(): bool
    {
        return $this->bucketName !== null && $this->serviceAccount !== null;
    }

    /**
     * GCS에 파일 업로드
     *
     * @param  string  $filePath  로컬 파일 경로
     * @param  string  $objectName  GCS에 저장할 객체 이름
     * @return string|null GCS URI (gs://bucket/object) 또는 실패 시 null
     */
    public function upload(string $filePath, string $objectName): ?string
    {
        if (! $this->isAvailable()) {
            Log::warning('GCS 업로드 실패: 설정되지 않음');

            return null;
        }

        if (! file_exists($filePath)) {
            Log::error('GCS 업로드 실패: 파일 없음 - '.$filePath);

            return null;
        }

        // OAuth 2.0 토큰 생성
        $accessToken = $this->getAccessToken();
        if (! $accessToken) {
            return null;
        }

        // GCS에 파일 업로드
        $fileContent = file_get_contents($filePath);
        $mimeType = mime_content_type($filePath) ?: 'application/octet-stream';

        $uploadUrl = 'https://storage.googleapis.com/upload/storage/v1/b/'.
            urlencode($this->bucketName).'/o?uploadType=media&name='.
            urlencode($objectName);

        $ch = curl_init($uploadUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer '.$accessToken,
            'Content-Type: '.$mimeType,
            'Content-Length: '.strlen($fileContent),
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContent);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 300); // 5분 타임아웃

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($httpCode === 200) {
            $gcsUri = 'gs://'.$this->bucketName.'/'.$objectName;
            Log::info('GCS 업로드 성공: '.$gcsUri);

            return $gcsUri;
        }

        Log::error('GCS 업로드 실패 (HTTP '.$httpCode.'): '.($error ?: $response));

        return null;
    }

    /**
     * GCS에서 서명된 다운로드 URL 생성
     *
     * @param  string  $objectName  GCS 객체 이름 또는 gs:// URI
     * @param  int  $expiresInMinutes  URL 유효 시간 (분)
     * @return string|null 서명된 URL 또는 실패 시 null
     */
    public function getSignedUrl(string $objectName, int $expiresInMinutes = 60): ?string
    {
        if (! $this->isAvailable()) {
            return null;
        }

        $objectName = $this->stripGsPrefix($objectName);

        $expiration = time() + ($expiresInMinutes * 60);
        $stringToSign = "GET\n\n\n{$expiration}\n/{$this->bucketName}/{$objectName}";

        $privateKey = openssl_pkey_get_private($this->serviceAccount['private_key']);
        if (! $privateKey) {
            Log::error('GCS URL 서명 실패: 개인 키 읽기 오류');

            return null;
        }

        openssl_sign($stringToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);
        if (PHP_VERSION_ID < 80000) {
            openssl_free_key($privateKey);
        }

        $encodedSignature = urlencode(base64_encode($signature));
        $clientEmail = urlencode($this->serviceAccount['client_email']);

        return "https://storage.googleapis.com/{$this->bucketName}/{$objectName}".
            "?GoogleAccessId={$clientEmail}".
            "&Expires={$expiration}".
            "&Signature={$encodedSignature}";
    }

    /**
     * GCS에서 파일 삭제
     *
     * @param  string  $objectName  GCS 객체 이름 또는 gs:// URI
     * @return bool 성공 여부
     */
    public function delete(string $objectName): bool
    {
        if (! $this->isAvailable()) {
            return false;
        }

        $objectName = $this->stripGsPrefix($objectName);

        $accessToken = $this->getAccessToken();
        if (! $accessToken) {
            return false;
        }

        $deleteUrl = 'https://storage.googleapis.com/storage/v1/b/'.
            urlencode($this->bucketName).'/o/'.
            urlencode($objectName);

        $ch = curl_init($deleteUrl);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer '.$accessToken,
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return $httpCode === 204 || $httpCode === 200;
    }

    /**
     * OAuth 2.0 액세스 토큰 획득
     */
    private function getAccessToken(): ?string
    {
        // JWT 생성
        $now = time();
        $jwtHeader = $this->base64UrlEncode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
        $jwtClaim = $this->base64UrlEncode(json_encode([
            'iss' => $this->serviceAccount['client_email'],
            'scope' => 'https://www.googleapis.com/auth/devstorage.full_control',
            'aud' => 'https://oauth2.googleapis.com/token',
            'exp' => $now + 3600,
            'iat' => $now,
        ]));

        $privateKey = openssl_pkey_get_private($this->serviceAccount['private_key']);
        if (! $privateKey) {
            Log::error('GCS 토큰 실패: 개인 키 읽기 오류');

            return null;
        }

        openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
        if (PHP_VERSION_ID < 80000) {
            openssl_free_key($privateKey);
        }

        $jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature);

        // OAuth 토큰 요청
        $ch = curl_init('https://oauth2.googleapis.com/token');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' => $jwt,
        ]));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode !== 200) {
            Log::error('GCS 토큰 실패: HTTP '.$httpCode);

            return null;
        }

        $data = json_decode($response, true);

        return $data['access_token'] ?? null;
    }

    /**
     * Base64 URL 인코딩
     */
    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    /**
     * gs://bucket-name/objectName → objectName 변환
     *
     * upload()가 gs:// URI를 반환하므로, getSignedUrl/delete에서 자동 처리
     */
    private function stripGsPrefix(string $objectName): string
    {
        $prefix = 'gs://'.$this->bucketName.'/';
        if (str_starts_with($objectName, $prefix)) {
            return substr($objectName, strlen($prefix));
        }

        // gs://다른-버킷/ 형태도 처리
        if (str_starts_with($objectName, 'gs://')) {
            $objectName = preg_replace('#^gs://[^/]+/#', '', $objectName);
        }

        return $objectName;
    }

    /**
     * 버킷 이름 반환
     */
    public function getBucketName(): ?string
    {
        return $this->bucketName;
    }
}
