<?php

namespace Inside\BCLH\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Inside\BCLH\Events\BulkCSVImportedEvent;
use Inside\BCLH\Events\BulkCSVImportFailedEvent;
use Inside\BCLH\Events\BulkExcelGeneratedEvent;
use Inside\BCLH\Exceptions\SopImportException;
use Inside\BCLH\Imports\SopsImport;
use Inside\Content\Models\Content;
use Inside\Content\Models\Contents\Sops;
use Inside\Workflow\Models\Proposal;
use Maatwebsite\Excel\Excel;

class ImportSopsFromExcel implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    private const PENDING_WORKFLOW = 2;

    protected string $userUuid;

    protected string $file;

    protected array $errors = [];

    protected array $jobs = [];

    protected array $pendingProposals = [];

    protected int $date;

    protected int $numberProcessedDocuments = 0;

    public function __construct(string $userUuid, string $file)
    {
        $this->userUuid = $userUuid;
        $this->file = $file;
        $this->date = time();
    }

    public function handle(): void
    {
        try {
            if (! Storage::disk('local')->exists($this->file)) {
                throw new SopImportException("Missing file import $this->file");
            }

            $collection = (new SopsImport)->toCollection(
                $this->file,
                'local',
                Excel::XLSX
            );

            $activeSheed = $collection->first();

            $this->pendingProposals = Proposal::where('status', self::PENDING_WORKFLOW)
                ->pluck('proposable_uuid')
                ->toArray();

            $activeSheed->each(fn ($row) => $this->processRow($row));

            $this->jobs[] = new DispatchEvent(
                eventClass: BulkCSVImportedEvent::class,
                userId: $this->userUuid,
                payload: $this->numberProcessedDocuments
            );

            if (! empty($this->errors)) {
                $this->jobs[] = new ExportSopsFailureReasonsInExcel($this->userUuid, $this->errors);
            }

            $this->chain($this->jobs);
        } catch (\Exception $exception) {
            event(new BulkCSVImportFailedEvent($this->userUuid));
            throw new SopImportException($exception->getMessage());
        }
    }

    private function processRow(Collection $row): void
    {
        if ($row->first() === 'uuid_host') {
            if (array_diff($row->toArray(), SopsImport::HEADER) !== []) {
                Log::info('Incorrect Headers from file', [
                    'sent headers' => $row->toArray(),
                    'expected headers' => SopsImport::HEADER,
                ]);
                throw new SopImportException('Headers are not correct');
            }

            return;
        }

        $row = collect(SopsImport::HEADER)->combine($row);

        if (empty($row['uuid_host'])) {
            $this->flagAsError($row, 'missing uuid host');

            return;
        }

        $contentType = $row['content_type'];

        /**
         * @var Collection $contents
         */
        $contents = type_to_class($contentType)::where('uuid_host', $row['uuid_host'])->get();

        if ($contents->isEmpty()) {
            $this->flagAsError($row, 'Content not found');

            return;
        }

        $isUnpublishedOrInWorkflow = $contents->some(function (Content $content) {
            return $content->status != '1' || in_array($content->uuid, $this->pendingProposals);
        });

        if ($isUnpublishedOrInWorkflow) {
            $this->flagAsError($row, 'Content in workflow or unpublished');

            return;
        }

        $referenceFields = ['brands', 'countries', 'management_modes', 'profiles'];

        $contents->each(function ($content) use ($referenceFields, $row) {
            try {
                if (! empty($this->errors[$row['uuid_host']])) {
                    return;
                }
                $data = [
                    'type' => 'node',
                    'bundle' => $row['content_type'],
                    'uuid' => $content->uuid,
                    'uuid_host' => $row['uuid_host'],
                    'langcode' => $content->langcode,
                    'last_update' => $this->date,
                ];

                collect($referenceFields)->each(function ($referenceField) use (&$data, $row) {
                    $data[$referenceField] = $this->getUuidsByCode($referenceField, $row, $data['langcode']);
                });

                if (! empty($row['contact'])) {
                    $data['users'] = $this->getContacts($row['contact']);
                }

                if (! empty($this->errors[$row['uuid_host']])) {
                    return;
                }

                $this->jobs[] = new BulkUpdate($data['bundle'], $data);
            } catch (\Exception $exception) {
                $this->flagAsError($row, $exception->getMessage());
            }
        });
        $this->numberProcessedDocuments++;
    }

    private function getContacts(string $contacts): array
    {
        $emails = explode(',', $contacts);
        $emails = array_map('trim', $emails);

        $return = call_user_func(type_to_class('users').'::query')
                ->whereIn('email', $emails)
                ->where('status', 1)
                ->pluck('uuid')
                ->toArray();

        if (count($emails) !== count($return)) {
            throw new SopImportException('At least one user not found or disabled');
        }

        return $return;
    }

    private function getUuidsByCode(string $contentType, Collection $row, string $langcode): array
    {
        $codesString = $row[$contentType];

        if ($codesString === '' || $codesString === null) {
            return [];
        }

        if ($codesString === 'all') {
            return call_user_func(type_to_class($contentType).'::query')
                ->where('langcode', '=', $langcode)
                ->pluck('uuid')
                ->toArray();
        }

        $codes = explode(',', $codesString);
        $codes = array_map('trim', $codes);

        $return = call_user_func(type_to_class($contentType).'::query')
                ->where('langcode', '=', $langcode)
                ->whereIn('code', $codes)
                ->pluck('uuid')
                ->toArray();

        if (count($codes) !== count($return)) {
            $this->flagAsError($row, "At least one $contentType reference not found");

            return [];
        }

        return $return;
    }

    private function flagAsError(Collection $row, string $reason): void
    {
        $row['reason'] = $row['reason'].','.$reason;
        $this->errors[$row['uuid_host']] = $row;
    }
}
