<?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\DB;
use Illuminate\Support\Facades\Storage;
use Inside\BCLH\Events\BulkCSVGeneratedEvent;
use League\Csv\Writer;

class GenerateBulkCSV implements ShouldQueue
{
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public const CSV_DOWNLOAD_FOLDER = 'csv/download';

    public const CSV_HEADER = [
        'uuid',
        'content_type',
        'title',
        'brands',
        'countries',
        'management_modes',
        'profiles',
        'contact',
        'folder',
        'parent',
    ];

    /**
     * @var string
     */
    protected $userUuid;

    /**
     * @var array
     */
    protected $contents;

    /**
     * @var bool
     */
    protected $filtered;

    public function __construct(string $userUuid, array $contents, bool $filtered)
    {
        $this->userUuid = $userUuid;
        $this->contents = $contents;
        $this->filtered = $filtered;
    }

    public function handle(): void
    {
        Storage::disk('protected')->makeDirectory(self::CSV_DOWNLOAD_FOLDER);

        $filePath = self::CSV_DOWNLOAD_FOLDER.'/'.$this->userUuid.'.csv';

        $formattedContent = $this->formatContents();
        $csv = Writer::createFromPath(Storage::disk('protected')->path($filePath), 'w+')
            ->setDelimiter(';');
        $csv->insertOne(self::CSV_HEADER);
        $csv->insertAll($formattedContent);

        event(new BulkCSVGeneratedEvent($this->userUuid, $filePath));

        $this->delete();
    }

    /**
     * @param mixed $content
     * @param mixed|null $parent
     * @return array
     */
    public function getFormattedContent($content, $parent = null): array
    {
        $formattedContent = [
            'uuid' => $content->uuid,
            'content_type' => $content->content_type,
            'title' => $content->title,
            'brands' => $this->getCodes($content, 'brands'),
            'countries' => $this->getCodes($content, 'countries'),
            'management_modes' => $this->getCodes($content, 'managementModes'),
            'profiles' => $this->getCodes($content, 'profiles'),
            'contact' => '',
            'folder' => '',
            'parent' => '',
        ];

        if (in_array($content->content_type, ['sops', 'rops', 'documents'])) {
            if ($parent) {
                $formattedContent['folder'] = $this->implodeBy($parent->folders, 'title');
            } else {
                $formattedContent['folder'] = $this->implodeBy($content->folders, 'title');
            }
        }

        if (in_array($content->content_type, ['rops', 'sops'])) {
            $formattedContent['contact'] = $this->implodeBy($content->users, 'email');
        }

        if ($content->content_type === 'declined_documents') {
            $formattedContent['parent'] = $this->implodeBy($content->documents, 'title');
        }

        if ($content->content_type === 'rops') {
            $formattedContent['parent'] = $this->implodeBy($content->sops, 'title');
        }

        return $formattedContent;
    }

    public function formatContents(): array
    {
        $formattedContents = [];
        foreach ($this->contents as $content) {
            $content = call_user_func(type_to_class($content['content_type']).'::find', $content['uuid']);

            $formattedContent = $this->getFormattedContent($content);

            $formattedContents[] = $formattedContent;

            if (! $this->filtered && in_array($content->content_type, ['sops', 'documents'])) {
                $declinedContentType = $content->content_type === 'sops' ? 'rops' : 'declined_documents';

                $query = call_user_func(type_to_class($declinedContentType).'::query');

                $declinedContents = $query->whereIn(type_to_table($declinedContentType).'.uuid', DB::table('inside_pivots')
                    ->where('related_type', get_class($content))
                    ->where('parent_type', type_to_class($declinedContentType))
                    ->where('related_uuid', $content->uuid)
                    ->pluck('parent_uuid'))
                    ->where('langcode', $content->langcode)
                    ->get();

                foreach ($declinedContents as $declinedContent) {
                    $formattedContent = $this->getFormattedContent($declinedContent, $content);

                    $formattedContents[] = $formattedContent;
                }
            }
        }

        return $formattedContents;
    }

    private function implodeBy(Collection $collection, string $attribute): string
    {
        return $collection->map(
            function ($item) use ($attribute) {
                return $item->{$attribute};
            }
        )->implode(',');
    }

    /**
     * @param mixed $content
     * @param string $attribute
     * @return string
     */
    private function getCodes($content, string $attribute): string
    {
        $max = call_user_func(type_to_class($attribute).'::query')
            ->where('langcode', '=', $content->langcode)
            ->count();

        if ($content->{$attribute}->count() === $max) {
            return 'all';
        }

        return $this->implodeBy($content->{$attribute}, 'code');
    }
}
