<?php

namespace App\Services\Package;

use App\Models\JobPackage;
use App\Models\JobDocument;
use App\Repositories\Package\PackageRepository;
use App\Events\Package\PackageCreated;
use App\Events\Package\PackageUpdated;
use App\Events\Package\PackageSubmitted;
use App\Jobs\ProcessMatchingJob;
use App\Exceptions\PackageException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Pagination\LengthAwarePaginator;

/**
 * Service untuk Package
 */
class PackageService
{
    protected PackageRepository $repository;

    /**
     * Cache key prefixes
     */
    private const CACHE_KEY_LIST = 'packages_list_';
    private const CACHE_KEY_SINGLE = 'package_';
    private const CACHE_KEY_STATS = 'packages_stats_';

    public function __construct(PackageRepository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * Get all packages with filters
     */
    public function getAll(array $filters = []): LengthAwarePaginator
    {
        $cacheKey = self::CACHE_KEY_LIST . md5(json_encode($filters));
        return Cache::remember($cacheKey, 3600, function () use ($filters) {
            return $this->repository->getAll($filters);
        });
    }

    /**
     * Get single package by ID
     */
    public function find(int $id): JobPackage
    {
        $cacheKey = self::CACHE_KEY_SINGLE . $id;
        return Cache::remember($cacheKey, 3600, function () use ($id) {
            return $this->repository->find($id);
        });
    }

    /**
     * Create new package
     */
    public function create(array $data): JobPackage
    {
        return DB::transaction(function () use ($data) {
            // 1. Validasi tambahan
            $this->_validateData($data);

            // 2. Set default status
            $data['status'] = JobPackage::STATUS_DRAFT;
            $data['ppk_id'] = auth()->id();

            // 3. Create package
            $package = $this->repository->create($data);

            // 4. Upload documents
            if (isset($data['documents']) && !empty($data['documents'])) {
                $this->_uploadDocuments($package, $data['documents']);
            }

            // 5. Dispatch event
            event(new PackageCreated($package));

            // 6. Clear cache
            $this->_clearCache();

            // 7. Log
            Log::info('Package created', [
                'package_id' => $package->id,
                'ppk_id' => auth()->id(),
                'budget' => $package->budget,
            ]);

            return $package->fresh();
        });
    }

    /**
     * Update package
     */
    public function update(JobPackage $package, array $data): JobPackage
    {
        return DB::transaction(function () use ($package, $data) {
            // 1. Check if package can be updated
            if (!$package->isOpen() && $package->status !== JobPackage::STATUS_DRAFT) {
                throw new PackageException('Paket tidak bisa diupdate karena sudah dalam proses');
            }

            // 2. Validate data
            $this->_validateData($data, $package->id);

            // 3. Update package
            $this->repository->update($package, $data);

            // 4. Upload new documents
            if (isset($data['documents']) && !empty($data['documents'])) {
                $this->_uploadDocuments($package, $data['documents']);
            }

            // 5. Dispatch event
            event(new PackageUpdated($package));

            // 6. Clear cache
            $this->_clearCache($package->id);

            // 7. Log
            Log::info('Package updated', [
                'package_id' => $package->id,
                'ppk_id' => auth()->id(),
            ]);

            return $package->fresh();
        });
    }

    /**
     * Delete package
     */
    public function delete(JobPackage $package): bool
    {
        return DB::transaction(function () use ($package) {
            // 1. Check if package can be deleted
            if (!$package->isOpen() && $package->status !== JobPackage::STATUS_DRAFT) {
                throw new PackageException('Paket tidak bisa dihapus karena sudah dalam proses');
            }

            // 2. Delete documents
            foreach ($package->documents as $doc) {
                Storage::disk('public')->delete($doc->file_url);
                $doc->delete();
            }

            // 3. Delete package
            $result = $this->repository->delete($package);

            // 4. Clear cache
            $this->_clearCache($package->id);

            // 5. Log
            Log::info('Package deleted', [
                'package_id' => $package->id,
                'ppk_id' => auth()->id(),
            ]);

            return $result;
        });
    }

    /**
     * Activate package (draft → active)
     */
    public function activate(JobPackage $package): JobPackage
    {
        return DB::transaction(function () use ($package) {
            // 1. Check if package can be activated
            if (!$package->isDraft()) {
                throw new PackageException('Hanya paket dengan status draft yang bisa diaktifkan');
            }

            // 2. Update status
            $package->markAsActive();

            // 3. Clear cache
            $this->_clearCache($package->id);

            // 4. Log
            Log::info('Package activated', [
                'package_id' => $package->id,
                'ppk_id' => auth()->id(),
            ]);

            return $package->fresh();
        });
    }

    /**
     * Submit package for matching
     */
    public function submit(JobPackage $package): JobPackage
    {
        return DB::transaction(function () use ($package) {
            // 1. Check if package can be submitted
            if (!$package->canBeMatched()) {
                throw new PackageException('Paket tidak bisa disubmit karena status: ' . $package->status);
            }

            // 2. Validate package completeness
            $this->_validateCompleteness($package);

            // 3. Update status
            $package->markAsMatching();

            // 4. Dispatch matching job (queue)
            ProcessMatchingJob::dispatch($package->id);

            // 5. Dispatch event
            event(new PackageSubmitted($package));

            // 6. Clear cache
            $this->_clearCache($package->id);

            // 7. Log
            Log::info('Package submitted for matching', [
                'package_id' => $package->id,
                'ppk_id' => auth()->id(),
            ]);

            return $package->fresh();
        });
    }

    /**
     * Cancel package
     */
    public function cancel(JobPackage $package, ?string $reason = null): JobPackage
    {
        return DB::transaction(function () use ($package, $reason) {
            // 1. Check if package can be cancelled
            if ($package->isCompleted()) {
                throw new PackageException('Paket yang sudah selesai tidak bisa dibatalkan');
            }

            // 2. Cancel package
            $package->cancel($reason);

            // 3. Clear cache
            $this->_clearCache($package->id);

            // 4. Log
            Log::info('Package cancelled', [
                'package_id' => $package->id,
                'ppk_id' => auth()->id(),
                'reason' => $reason,
            ]);

            return $package->fresh();
        });
    }

    /**
     * 👇 INI YANG DITAMBAH! Upload document to package
     */
    public function uploadDocument(JobPackage $package, $file): JobDocument
    {
        return DB::transaction(function () use ($package, $file) {
            // 1. Validate file
            if (!$file->isValid()) {
                throw new PackageException('File tidak valid');
            }

            // 2. Store file
            $path = $file->store('packages/' . $package->id . '/documents', 'public');

            if (!$path) {
                throw new PackageException('Gagal upload dokumen: ' . $file->getClientOriginalName());
            }

            // 3. Create document record
            $document = $package->documents()->create([
                'name' => $file->getClientOriginalName(),
                'file_url' => $path,
                'type' => $file->getClientMimeType(),
                'size' => $file->getSize(),
                'uploaded_by' => auth()->id(),
            ]);

            // 4. Clear cache
            $this->_clearCache($package->id);

            // 5. Log
            Log::info('Document uploaded', [
                'package_id' => $package->id,
                'document_id' => $document->id,
                'uploaded_by' => auth()->id(),
                'file_name' => $file->getClientOriginalName(),
                'file_size' => $file->getSize(),
            ]);

            return $document;
        });
    }

    /**
     * Get PPK packages
     */
    public function getPpkPackages(int $ppkId, array $filters = []): LengthAwarePaginator
    {
        return $this->repository->getByPpk($ppkId, $filters);
    }

    /**
     * Get matching queue
     */
    public function getMatchingQueue(): \Illuminate\Support\Collection
    {
        return $this->repository->getMatchingQueue();
    }

    /**
     * Get statistics
     */
    public function getStatistics(?int $ppkId = null): array
    {
        $cacheKey = self::CACHE_KEY_STATS . ($ppkId ?? 'all');
        return Cache::remember($cacheKey, 1800, function () use ($ppkId) {
            return $this->repository->getStatistics($ppkId);
        });
    }

    /**
     * Private: Validate data
     */
    private function _validateData(array $data, ?int $excludeId = null): void
    {
        if (isset($data['budget']) && $data['budget'] < 0) {
            throw new PackageException('Pagu anggaran tidak boleh negatif');
        }

        if (isset($data['duration_days']) && $data['duration_days'] < 1) {
            throw new PackageException('Jangka waktu minimal 1 hari');
        }

        if (isset($data['duration_days']) && $data['duration_days'] > 365) {
            throw new PackageException('Jangka waktu maksimal 365 hari');
        }

        // Check duplicate name
        if (isset($data['name'])) {
            $query = JobPackage::where('name', $data['name']);
            if ($excludeId) {
                $query->where('id', '!=', $excludeId);
            }
            if ($query->exists()) {
                throw new PackageException('Nama paket sudah digunakan');
            }
        }
    }

    /**
     * Private: Validate package completeness before submit
     */
    private function _validateCompleteness(JobPackage $package): void
    {
        $requiredFields = ['name', 'job_type', 'sub_classification', 'budget', 'province', 'regency', 'district', 'village', 'duration_days'];

        foreach ($requiredFields as $field) {
            if (empty($package->$field)) {
                throw new PackageException("Field '$field' harus diisi sebelum submit");
            }
        }

        if ($package->documents()->count() === 0) {
            throw new PackageException('Minimal 1 dokumen harus diupload sebelum submit');
        }
    }

    /**
     * Private: Upload documents
     */
    private function _uploadDocuments(JobPackage $package, array $documents): void
    {
        foreach ($documents as $file) {
            $path = $file->store('packages/' . $package->id . '/documents', 'public');

            if (!$path) {
                throw new PackageException('Gagal upload dokumen: ' . $file->getClientOriginalName());
            }

            $package->documents()->create([
                'name' => $file->getClientOriginalName(),
                'file_url' => $path,
                'type' => $file->getClientMimeType(),
                'size' => $file->getSize(),
                'uploaded_by' => auth()->id(),
            ]);
        }
    }

    /**
     * Private: Clear cache
     */
    private function _clearCache(?int $packageId = null): void
    {
        // Clear list cache (all variations)
        Cache::forget(self::CACHE_KEY_LIST . '*');

        // Clear single cache
        if ($packageId) {
            Cache::forget(self::CACHE_KEY_SINGLE . $packageId);
        }

        // Clear stats cache
        Cache::forget(self::CACHE_KEY_STATS . '*');
    }
}