<?php

namespace App\Http\Controllers;

use App\Models\ProductVideo;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use RuntimeException;
use Throwable;

class VideoImportController extends Controller
{
    private $shop;
    private $token;
    private $apiVersion;

    public function __construct()
    {
        $this->shop = 'timele-classics.myshopify.com';
        $this->token = "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";
        $this->apiVersion = "2024-01";
    }

    /**
     * CSV columns supported:
     *
     * Handle,Variant SKU,video,alt
     *
     * Required:
     * - Handle
     * - video
     *
     * The CSV upload itself is read from PHP's uploaded-file stream.
     * Video bytes are never downloaded or saved on this Laravel server.
     */
    public function uploadCsv()
    {
        set_time_limit(0);
        ini_set('memory_limit', '1024M');

        try {
            $this->validateShopifyConfiguration();

            /*
             * CSV is read directly from storage/app.
             * No browser upload and no $request->file() are used.
             */
            $filePath = storage_path('app/video_12.csv');

            if (!file_exists($filePath)) {
                return response()->json([
                    'status' => false,
                    'message' => 'CSV file nahi mili: ' . $filePath,
                ], 404);
            }

            $stream = fopen($filePath, 'rb');

            if ($stream === false) {
                throw new RuntimeException('CSV file open nahi hui.');
            }

            $firstLine = fgets($stream);

            if ($firstLine === false) {
                fclose($stream);

                throw new RuntimeException('CSV file empty hai.');
            }

            $delimiter = $this->detectDelimiter($firstLine);
            rewind($stream);

            $rawHeader = fgetcsv($stream, 0, $delimiter);

            if (!$rawHeader) {
                fclose($stream);

                throw new RuntimeException('CSV header read nahi hua.');
            }

            $header = array_map(function ($column) {
                return $this->normalizeHeader((string) $column);
            }, $rawHeader);

            $handleIndex = $this->findHeaderIndex($header, [
                'handle',
                'product handle',
            ]);

            $skuIndex = $this->findHeaderIndex($header, [
                'variant sku',
                'sku',
            ]);

            $videoIndex = $this->findHeaderIndex($header, [
                'video',
                'video url',
                'video_url',
                'video link',
                'video_link',
            ]);

            $altIndex = $this->findHeaderIndex($header, [
                'alt',
                'video alt',
                'video_alt',
                'alt text',
            ]);

            if ($handleIndex === null || $videoIndex === null) {
                fclose($stream);

                throw new RuntimeException(
                    'CSV me Handle aur video columns required hain.'
                );
            }

            $summary = [
                'total_rows' => 0,
                'uploaded' => 0,
                'already_exists' => 0,
                'skipped' => 0,
                'failed' => 0,
            ];

            $results = [];
            $seenRows = [];

            while (($row = fgetcsv($stream, 0, $delimiter)) !== false) {
                $summary['total_rows']++;

                if ($this->isEmptyCsvRow($row)) {
                    $summary['skipped']++;
                    continue;
                }

                $handle = trim((string) ($row[$handleIndex] ?? ''));

                $variantSku = $skuIndex !== null
                    ? trim((string) ($row[$skuIndex] ?? ''))
                    : '';

                $videoUrl = trim((string) ($row[$videoIndex] ?? ''));

                $alt = $altIndex !== null
                    ? trim((string) ($row[$altIndex] ?? ''))
                    : '';

                if ($alt === '') {
                    $alt = $variantSku !== ''
                        ? 'Product video ' . $variantSku
                        : 'Product video ' . $handle;
                }

                if ($handle === '' || $videoUrl === '') {
                    $summary['skipped']++;

                    $results[] = [
                        'row' => $summary['total_rows'] + 1,
                        'status' => 'skipped',
                        'message' => 'Handle ya video URL blank hai.',
                    ];

                    continue;
                }

                /*
                 * Same CSV run me duplicate row ko skip karein.
                 */
                $rowKey = strtolower(
                    $handle . '|' . $variantSku . '|' . $videoUrl
                );

                if (isset($seenRows[$rowKey])) {
                    $summary['already_exists']++;

                    $results[] = [
                        'row' => $summary['total_rows'] + 1,
                        'handle' => $handle,
                        'variant_sku' => $variantSku,
                        'video_url' => $videoUrl,
                        'status' => 'duplicate_csv_row',
                    ];

                    continue;
                }

                $seenRows[$rowKey] = true;

                try {
                    $result = $this->processCsvVideoRow(
                        $handle,
                        $variantSku,
                        $videoUrl,
                        $alt
                    );

                    if (!empty($result['already_exists'])) {
                        $summary['already_exists']++;
                    } else {
                        $summary['uploaded']++;
                    }

                    $results[] = array_merge([
                        'row' => $summary['total_rows'] + 1,
                    ], $result);

                } catch (Throwable $rowError) {
                    $summary['failed']++;

                    Log::error('CSV Shopify video row failed.', [
                        'row' => $summary['total_rows'] + 1,
                        'handle' => $handle,
                        'variant_sku' => $variantSku,
                        'video_url' => $videoUrl,
                        'message' => $rowError->getMessage(),
                    ]);

                    $results[] = [
                        'row' => $summary['total_rows'] + 1,
                        'handle' => $handle,
                        'variant_sku' => $variantSku,
                        'video_url' => $videoUrl,
                        'status' => 'failed',
                        'message' => $rowError->getMessage(),
                    ];
                }

                usleep(500000);
            }

            fclose($stream);

            return response()->json([
                'status' => true,
                'message' => 'Storage CSV video import completed.',
                'file' => $filePath,
                'summary' => $summary,
                'results' => $results,
            ]);

        } catch (Throwable $e) {
            Log::error('Storage CSV Shopify video import failed.', [
                'message' => $e->getMessage(),
            ]);

            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Single-video endpoint.
     */
    public function upload(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'handle' => ['required', 'string', 'max:255'],
            'variant_sku' => ['nullable', 'string', 'max:255'],
            'video_url' => ['required', 'url', 'max:2048'],
            'alt' => ['nullable', 'string', 'max:512'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'status' => false,
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        try {
            $this->validateShopifyConfiguration();

            $handle = trim((string) $request->input('handle'));
            $variantSku = trim(
                (string) $request->input('variant_sku', '')
            );
            $videoUrl = trim((string) $request->input('video_url'));
            $alt = trim((string) $request->input('alt', ''));

            if ($alt === '') {
                $alt = $variantSku !== ''
                    ? "Product video {$variantSku}"
                    : "Product video {$handle}";
            }

            $result = $this->processCsvVideoRow(
                $handle,
                $variantSku,
                $videoUrl,
                $alt
            );

            return response()->json([
                'status' => true,
                'message' => $result['already_exists']
                    ? 'Video already exists.'
                    : 'Video uploaded from S3 to Shopify.',
                'data' => $result,
            ]);

        } catch (Throwable $e) {
            Log::error('Single Shopify video upload failed.', [
                'message' => $e->getMessage(),
            ]);

            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    private function processCsvVideoRow(
        $handle,
        $variantSku,
        $videoUrl,
        $alt
    ) {
        $metadata = $this->getRemoteVideoMetadata($videoUrl);

        /*
         * Prevent duplicate source-video mapping in the local database.
         */
        $existing = ProductVideo::query()
            ->where('handle', $handle)
            ->where('variant_sku', $variantSku)
            ->where('video_url', $videoUrl)
            ->first();

        if ($existing) {
            return [
                'handle' => $handle,
                'variant_sku' => $variantSku,
                'video_url' => $videoUrl,
                'status' => 'already_exists',
                'already_exists' => true,
                'record' => $existing,
            ];
        }

        $productId = $this->getProductIdByHandle($handle);

        if (!$productId) {
            throw new RuntimeException(
                "Shopify product not found for handle: {$handle}"
            );
        }

        /*
         * Correct video workflow:
         *
         * S3 response stream -> Shopify staged upload -> Shopify fileCreate.
         *
         * The MP4 is never written to storage/app, /tmp, public, or cPanel.
         */
        $target = $this->createStagedVideoUpload(
            $metadata['filename'],
            $metadata['mime_type'],
            $metadata['file_size']
        );

        $this->streamS3VideoToShopify(
            $videoUrl,
            $target,
            $metadata['filename'],
            $metadata['mime_type']
        );

        /*
         * Attach the staged upload URL directly to the product.
         * This is the correct Shopify product-media workflow.
         */
        $media = $this->attachStagedVideoToProduct(
            $productId,
            $target['resourceUrl'],
            $alt
        );

        $mediaId = $media['id'] ?? null;

        if (!$mediaId) {
            throw new RuntimeException(
                'Shopify productUpdate did not return a media ID.'
            );
        }

        /*
         * Save in the database only after Shopify accepted the media.
         */
        $saved = ProductVideo::create([
            'handle' => $handle,
            'variant_sku' => $variantSku,
            'product_id' => $productId,
            'video_url' => $videoUrl,
            'shopify_video_url' => $mediaId,
        ]);

        return [
            'handle' => $handle,
            'variant_sku' => $variantSku,
            'video_url' => $videoUrl,
            'product_id' => $productId,
            'shopify_media_id' => $mediaId,
            'media_status' => $media['status'] ?? null,
            'status' => 'uploaded',
            'already_exists' => false,
            'record' => $saved,
        ];
    }

    /*
    |--------------------------------------------------------------------------
    | GraphQL
    |--------------------------------------------------------------------------
    */

    private function graphql(
        $query,
        $variables = []
    ) {
        $response = Http::withHeaders([
            'X-Shopify-Access-Token' => $this->token,
            'Content-Type' => 'application/json',
        ])
            ->acceptJson()
            ->withOptions([
                'connect_timeout' => 30,
            ])
            ->timeout(120)
            ->retry(3, 1500)
            ->post(
                "https://{$this->shop}/admin/api/{$this->apiVersion}/graphql.json",
                [
                    'query' => $query,
                    'variables' => $variables,
                ]
            );

        if ($response->failed()) {
            throw new RuntimeException(
                "Shopify HTTP {$response->status()}: {$response->body()}"
            );
        }

        $json = $response->json();

        if (!is_array($json)) {
            throw new RuntimeException(
                'Shopify returned invalid JSON.'
            );
        }

        if (!empty($json['errors'])) {
            throw new RuntimeException(
                'Shopify GraphQL error: ' .
                json_encode(
                    $json['errors'],
                    JSON_UNESCAPED_SLASHES
                )
            );
        }

        return $json;
    }

    /*
    |--------------------------------------------------------------------------
    | Find product
    |--------------------------------------------------------------------------
    */

    private function getProductIdByHandle(
        $handle
    ) {
        $query = <<<'GQL'
query GetProductByHandle($query: String!) {
  products(first: 1, query: $query) {
    nodes {
      id
      handle
    }
  }
}
GQL;

        $response = $this->graphql($query, [
            'query' => 'handle:' .
                $this->escapeSearchValue($handle),
        ]);

        $product =
            $response['data']['products']['nodes'][0] ?? null;

        if (
            !$product ||
            ($product['handle'] ?? null) !== $handle
        ) {
            return null;
        }

        return $product['id'] ?? null;
    }

    /*
    |--------------------------------------------------------------------------
    | S3 URL -> Shopify Files
    |--------------------------------------------------------------------------
    */

    /*
    |--------------------------------------------------------------------------
    | Read remote video metadata without downloading the file
    |--------------------------------------------------------------------------
    */

    private function getRemoteVideoMetadata($videoUrl)
    {
        $scheme = strtolower(
            (string) parse_url($videoUrl, PHP_URL_SCHEME)
        );

        if ($scheme !== 'https') {
            throw new RuntimeException(
                'Video URL must use HTTPS.'
            );
        }

        $path = (string) parse_url(
            $videoUrl,
            PHP_URL_PATH
        );

        $filename = basename($path);

        if ($filename === '' || $filename === '/') {
            throw new RuntimeException(
                'Video filename could not be detected from the S3 URL.'
            );
        }

        $extension = strtolower(
            pathinfo($filename, PATHINFO_EXTENSION)
        );

        $mimeMap = [
            'mp4' => 'video/mp4',
            'mov' => 'video/quicktime',
            'webm' => 'video/webm',
        ];

        if (!isset($mimeMap[$extension])) {
            throw new RuntimeException(
                'Only MP4, MOV, and WEBM videos are supported.'
            );
        }

        $client = new \GuzzleHttp\Client([
            'connect_timeout' => 20,
            'timeout' => 60,
            'allow_redirects' => true,
            'http_errors' => false,
        ]);

        $response = $client->request('HEAD', $videoUrl);

        if (
            $response->getStatusCode() < 200 ||
            $response->getStatusCode() >= 400
        ) {
            throw new RuntimeException(
                'S3 video HEAD request failed. HTTP ' .
                $response->getStatusCode()
            );
        }

        $fileSize = (int) $response->getHeaderLine(
            'Content-Length'
        );

        if ($fileSize <= 0) {
            throw new RuntimeException(
                'S3 did not return Content-Length. Shopify requires the video file size.'
            );
        }

        $contentType = trim(
            $response->getHeaderLine('Content-Type')
        );

        if (
            $contentType === '' ||
            stripos($contentType, 'video/') !== 0
        ) {
            $contentType = $mimeMap[$extension];
        }

        return [
            'filename' => $filename,
            'mime_type' => $contentType,
            'file_size' => $fileSize,
        ];
    }

    /*
    |--------------------------------------------------------------------------
    | Step 1: Create Shopify staged upload target
    |--------------------------------------------------------------------------
    */

    private function createStagedVideoUpload(
        $filename,
        $mimeType,
        $fileSize
    ) {
        $mutation = <<<'GQL'
mutation CreateStagedVideoUpload($input: [StagedUploadInput!]!) {
  stagedUploadsCreate(input: $input) {
    stagedTargets {
      url
      resourceUrl
      parameters {
        name
        value
      }
    }
    userErrors {
      field
      message
    }
  }
}
GQL;

        $response = $this->graphql($mutation, [
            'input' => [[
                'filename' => $filename,
                'mimeType' => $mimeType,
                'resource' => 'VIDEO',
                'fileSize' => (string) $fileSize,
                'httpMethod' => 'POST',
            ]],
        ]);

        $payload =
            $response['data']['stagedUploadsCreate'] ?? null;

        if (!$payload) {
            throw new RuntimeException(
                'Shopify stagedUploadsCreate returned no payload.'
            );
        }

        $this->throwUserErrors(
            $payload['userErrors'] ?? [],
            'Shopify stagedUploadsCreate'
        );

        $target = $payload['stagedTargets'][0] ?? null;

        if (
            !$target ||
            empty($target['url']) ||
            empty($target['resourceUrl'])
        ) {
            throw new RuntimeException(
                'Shopify staged upload target was not returned.'
            );
        }

        return $target;
    }

    /*
    |--------------------------------------------------------------------------
    | Step 2: Stream S3 video directly to Shopify staged storage
    |--------------------------------------------------------------------------
    |
    | No local file is created. Guzzle streams the S3 response body directly
    | into Shopify's multipart upload request.
    |--------------------------------------------------------------------------
    */

    private function streamS3VideoToShopify(
        $videoUrl,
        $target,
        $filename,
        $mimeType
    ) {
        $client = new \GuzzleHttp\Client([
            'connect_timeout' => 30,
            'timeout' => 0,
            'allow_redirects' => true,
            'http_errors' => false,
        ]);

        $sourceResponse = $client->request(
            'GET',
            $videoUrl,
            [
                'stream' => true,
                'headers' => [
                    'Accept' => 'video/*,*/*',
                ],
            ]
        );

        if (
            $sourceResponse->getStatusCode() < 200 ||
            $sourceResponse->getStatusCode() >= 300
        ) {
            throw new RuntimeException(
                'S3 video stream failed. HTTP ' .
                $sourceResponse->getStatusCode()
            );
        }

        $multipart = [];

        foreach (($target['parameters'] ?? []) as $parameter) {
            $multipart[] = [
                'name' => $parameter['name'],
                'contents' => $parameter['value'],
            ];
        }

        /*
         * The file part must be added after Shopify's signed parameters.
         */
        $multipart[] = [
            'name' => 'file',
            'contents' => $sourceResponse->getBody(),
            'filename' => $filename,
            'headers' => [
                'Content-Type' => $mimeType,
            ],
        ];

        $uploadResponse = $client->request(
            'POST',
            $target['url'],
            [
                'multipart' => $multipart,
            ]
        );

        $status = $uploadResponse->getStatusCode();

        if (!in_array($status, [200, 201, 204], true)) {
            throw new RuntimeException(
                'Shopify staged binary upload failed. HTTP ' .
                $status .
                '. Response: ' .
                substr(
                    (string) $uploadResponse->getBody(),
                    0,
                    1000
                )
            );
        }
    }

    /*
    |--------------------------------------------------------------------------
    | Step 3: Attach staged video directly to the Shopify product
    |--------------------------------------------------------------------------
    */

    private function attachStagedVideoToProduct(
        $productId,
        $resourceUrl,
        $alt
    ) {
        $mutation = <<<'GQL'
mutation AddVideoToProduct(
  $product: ProductUpdateInput!,
  $media: [CreateMediaInput!]
) {
  productUpdate(product: $product, media: $media) {
    product {
      id
      media(first: 20, reverse: true) {
        nodes {
          id
          alt
          mediaContentType
          status
          preview {
            status
          }
        }
      }
    }
    userErrors {
      field
      message
    }
  }
}
GQL;

        $response = $this->graphql($mutation, [
            'product' => [
                'id' => $productId,
            ],
            'media' => [[
                'mediaContentType' => 'VIDEO',
                'originalSource' => $resourceUrl,
                'alt' => $alt,
            ]],
        ]);

        $payload =
            $response['data']['productUpdate'] ?? null;

        if (!$payload) {
            throw new RuntimeException(
                'Shopify productUpdate returned no payload.'
            );
        }

        $this->throwUserErrors(
            $payload['userErrors'] ?? [],
            'Shopify productUpdate'
        );

        $nodes =
            $payload['product']['media']['nodes'] ?? [];

        foreach ($nodes as $node) {
            if (
                ($node['mediaContentType'] ?? null) === 'VIDEO' &&
                ($node['alt'] ?? '') === $alt
            ) {
                return $node;
            }
        }

        /*
         * Fallback: return the newest video node.
         */
        foreach ($nodes as $node) {
            if (
                ($node['mediaContentType'] ?? null) === 'VIDEO'
            ) {
                return $node;
            }
        }

        throw new RuntimeException(
            'Shopify accepted productUpdate but returned no video media.'
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Remote URL validation without video download
    |--------------------------------------------------------------------------
    */

    private function validateRemoteVideoUrl(
        $videoUrl
    ) {
        $scheme = strtolower(
            (string) parse_url(
                $videoUrl,
                PHP_URL_SCHEME
            )
        );

        $path = strtolower(
            (string) parse_url(
                $videoUrl,
                PHP_URL_PATH
            )
        );

        if ($scheme !== 'https') {
            throw new RuntimeException(
                'Video URL must use HTTPS.'
            );
        }

        if (
            !$this->endsWith($path, '.mp4') &&
            !$this->endsWith($path, '.mov') &&
            !$this->endsWith($path, '.webm')
        ) {
            throw new RuntimeException(
                'Video URL must point to MP4, MOV, or WEBM.'
            );
        }

        /*
         * HEAD checks accessibility only.
         * It does not save the video locally.
         */
        $headResponse = Http::withOptions([
                'connect_timeout' => 15,
            ])
            ->timeout(30)
            ->withHeaders([
                'Accept' => 'video/*,*/*',
            ])
            ->head($videoUrl);

        if ($headResponse->successful()) {
            return;
        }

        /*
         * Some S3 endpoints reject HEAD. Request one byte as fallback.
         * The complete video is still not downloaded.
         */
        $rangeResponse = Http::withOptions([
                'connect_timeout' => 15,
            ])
            ->timeout(30)
            ->withHeaders([
                'Range' => 'bytes=0-0',
                'Accept' => 'video/*,*/*',
            ])
            ->get($videoUrl);

        if (
            !in_array(
                $rangeResponse->status(),
                [200, 206],
                true
            )
        ) {
            throw new RuntimeException(
                "Video URL is not accessible. HTTP {$rangeResponse->status()}."
            );
        }
    }

    private function validateShopifyConfiguration()
    {
        if ($this->shop === '' || $this->token === '') {
            throw new RuntimeException(
                'Shopify shop domain or access token is missing.'
            );
        }

        if (!$this->endsWith(
            $this->shop,
            '.myshopify.com'
        )) {
            throw new RuntimeException(
                'SHOPIFY_SHOP must be the full *.myshopify.com domain.'
            );
        }
    }

    private function throwUserErrors(
        $errors,
        $operation
    ) {
        if (empty($errors)) {
            return;
        }

        $message = collect($errors)
            ->map(function (array $error) {
                $field = implode(
                    '.',
                    $error['field'] ?? []
                );

                return trim(
                    $field . ': ' .
                    ($error['message'] ?? 'Unknown error'),
                    ': '
                );
            })
            ->implode(' | ');

        throw new RuntimeException(
            "{$operation} error: {$message}"
        );
    }

    private function detectDelimiter(
        $firstLine
    ) {
        $delimiters = [
            ',' => substr_count($firstLine, ','),
            "\t" => substr_count($firstLine, "\t"),
            ';' => substr_count($firstLine, ';'),
            '|' => substr_count($firstLine, '|'),
        ];

        arsort($delimiters);

        return (string) array_key_first($delimiters);
    }

    private function normalizeHeader(
        $value
    ) {
        $value = preg_replace(
            '/^\xEF\xBB\xBF/',
            '',
            $value
        );

        $value = strtolower(trim($value));

        return preg_replace(
            '/\s+/',
            ' ',
            $value
        ) ?? $value;
    }

    private function findHeaderIndex(
        $header,
        $acceptedNames
    ) {
        foreach ($acceptedNames as $acceptedName) {
            $index = array_search(
                strtolower($acceptedName),
                $header,
                true
            );

            if ($index !== false) {
                return $index;
            }
        }

        return null;
    }

    private function isEmptyCsvRow(
        $row
    ) {
        foreach ($row as $value) {
            if (trim((string) $value) !== '') {
                return false;
            }
        }

        return true;
    }

    private function escapeSearchValue(
        $value
    ) {
        return '"' .
            addcslashes($value, '\\"') .
            '"';
    }

    private function endsWith($haystack, $needle)
    {
        if ($needle === '') {
            return true;
        }

        return substr($haystack, -strlen($needle)) === $needle;
    }

}
