<?php

namespace App\Services\Files;

use App\Models\FileBucket;
use App\Models\FileChunk;
use App\Models\UploadSession;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class ChunkedUploadService
{
    public function __construct(
        protected FileStorageService $storageService,
        protected FileValidationService $validationService
    ) {}

    /**
     * Calculate chunk information for a file.
     */
    public function calculateChunks(int $fileSize, ?int $chunkSize = null): array
    {
        $chunkSize = $chunkSize ?? config('file_upload.default_chunk_size', 5242880);

        return [
            'chunk_size' => $chunkSize,
            'total_chunks' => (int) ceil($fileSize / $chunkSize),
            'total_size' => $fileSize,
        ];
    }

    /**
     * Create a new upload session.
     */
    public function createSession(
        FileBucket $bucket,
        string $originalName,
        int $fileSize,
        ?string $mimeType = null,
        ?int $chunkSize = null
    ): UploadSession {
        $chunkInfo = $this->calculateChunks($fileSize, $chunkSize);

        return UploadSession::create([
            'user_id' => auth()->id(),
            'bucket_id' => $bucket->id,
            'original_name' => $originalName,
            'total_size' => $fileSize,
            'chunk_size' => $chunkInfo['chunk_size'],
            'total_chunks' => $chunkInfo['total_chunks'],
            'mime_type' => $mimeType,
            'status' => 'pending',
        ]);
    }

    /**
     * Store a chunk.
     */
    public function storeChunk(UploadSession $session, UploadedFile $chunk, int $index): FileChunk
    {
        if (! $session->isActive()) {
            throw new RuntimeException('Upload session is not active.');
        }

        if ($session->isExpired()) {
            throw new RuntimeException('Upload session has expired.');
        }

        // Validate chunk index
        if ($index < 0 || $index >= $session->total_chunks) {
            throw new RuntimeException("Invalid chunk index: {$index}");
        }

        // Check if chunk already uploaded
        if (FileChunk::where('upload_session_id', $session->id)
            ->where('chunk_index', $index)
            ->exists()) {
            throw new RuntimeException("Chunk {$index} already uploaded.");
        }

        $content = file_get_contents($chunk->getRealPath());
        $checksum = $this->validationService->calculateChecksumFromContent($content);

        // Validate first chunk for MIME type
        if ($index === 0) {
            $validation = $this->validationService->validateFirstChunk(
                $content,
                $session->original_name,
                $session->bucket->allowed_mime_types
            );

            // Update session with detected MIME type
            $session->update([
                'mime_type' => $validation['mime_type'],
            ]);
        }

        // Store chunk in S3
        $chunkPath = $this->storageService->storeChunk($content, $session->uuid, $index);

        // Create chunk record
        $fileChunk = FileChunk::create([
            'upload_session_id' => $session->id,
            'chunk_index' => $index,
            'chunk_path' => $chunkPath,
            'chunk_size' => strlen($content),
            'checksum' => $checksum,
            'uploaded_at' => now(),
        ]);

        // Mark chunk as uploaded in session
        $session->markChunkUploaded($index);

        return $fileChunk;
    }

    /**
     * Assemble chunks into final file.
     */
    public function assembleChunks(UploadSession $session): string
    {
        if (! $session->isComplete()) {
            throw new RuntimeException('Not all chunks have been uploaded.');
        }

        $session->update(['status' => 'assembling']);

        try {
            // Get all chunks in order
            $chunks = FileChunk::where('upload_session_id', $session->id)
                ->orderBy('chunk_index')
                ->get();

            // Generate final file path
            $extension = pathinfo($session->original_name, PATHINFO_EXTENSION);
            $uuid = (string) \Illuminate\Support\Str::uuid();
            $storedName = "{$uuid}.{$extension}";
            $finalPath = $this->storageService->generatePath($session->bucket, $storedName);

            // Use S3 multipart upload for assembly
            $uploadId = $this->storageService->initiateMultipartUpload($finalPath);
            $parts = [];

            foreach ($chunks as $index => $chunk) {
                $content = $this->storageService->get($chunk->chunk_path);
                $etag = $this->storageService->uploadPart($finalPath, $uploadId, $index + 1, $content);

                $parts[] = [
                    'PartNumber' => $index + 1,
                    'ETag' => $etag,
                ];
            }

            $this->storageService->completeMultipartUpload($finalPath, $uploadId, $parts);

            // Calculate final checksum
            $finalChecksum = $this->calculateFinalChecksum($chunks);

            // Update session
            $session->update([
                'checksum' => $finalChecksum,
                'status' => 'completed',
            ]);

            // Clean up chunks
            $this->storageService->cleanupChunks($session->uuid);

            return $finalPath;
        } catch (\Exception $e) {
            $session->update(['status' => 'failed']);
            throw new RuntimeException('Failed to assemble file: '.$e->getMessage());
        }
    }

    /**
     * Calculate the final file checksum from all chunks.
     */
    protected function calculateFinalChecksum($chunks): string
    {
        $hashContext = hash_init('sha256');

        foreach ($chunks as $chunk) {
            $content = $this->storageService->get($chunk->chunk_path);
            hash_update($hashContext, $content);
        }

        return hash_final($hashContext);
    }

    /**
     * Validate chunk integrity.
     */
    public function validateChunkIntegrity(FileChunk $chunk, string $content): bool
    {
        return $chunk->verifyChecksum($content);
    }

    /**
     * Cancel an upload session and clean up.
     */
    public function cancelSession(UploadSession $session): void
    {
        // Clean up chunks from S3
        $this->storageService->cleanupChunks($session->uuid);

        // Delete chunk records
        FileChunk::where('upload_session_id', $session->id)->delete();

        // Update session status
        $session->update(['status' => 'failed']);
    }

    /**
     * Get upload progress.
     */
    public function getProgress(UploadSession $session): array
    {
        return [
            'session_id' => $session->uuid,
            'status' => $session->status,
            'total_chunks' => $session->total_chunks,
            'uploaded_chunks' => count($session->uploaded_chunks ?? []),
            'progress_percentage' => $session->getProgressPercentage(),
            'missing_chunks' => $session->getMissingChunks(),
            'expires_at' => $session->expires_at->toIso8601String(),
        ];
    }

    /**
     * Clean up expired sessions.
     */
    public function cleanupExpiredSessions(): int
    {
        $expiredSessions = UploadSession::where('expires_at', '<', now())
            ->whereIn('status', ['pending', 'uploading'])
            ->get();

        $count = 0;

        foreach ($expiredSessions as $session) {
            $this->cancelSession($session);
            $session->update(['status' => 'expired']);
            $count++;
        }

        return $count;
    }
}
