<?php

namespace Ignite\Core\Entities;

use Ignite\Core\Contracts\Repositories\UserRepository;
use Ignite\Core\Models\Import\LogFormatter;
use Ignite\Core\Models\Import\LogPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\HtmlString;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

/**
 * Class Import
 *
 * @property int $id
 * @property string $type
 * @property string|null $label
 * @property string|null $file
 * @property string|null $log
 * @property string|null $disk
 * @property int|null $records
 * @property int|null $imported
 * @property int|null $rejected
 * @property int|null $status
 * @property int $created_by
 * @property int|null $run_by
 * @property \Illuminate\Support\Carbon|null $run_at
 * @property \Illuminate\Support\Carbon|null $created_at
 * @property \Illuminate\Support\Carbon|null $updated_at
 * @property-read \Ignite\Core\Entities\User $createdBy
 * @property-read \Ignite\Core\Entities\User|null $runBy
 * @method static \Illuminate\Database\Eloquent\Builder|Import newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|Import newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|Import query()
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereCreatedAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereCreatedBy($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereDisk($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereFile($value)
 * @method static Builder|Base whereHasPermission(string $permission, ?\Ignite\Core\Entities\User $user = null)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereImported($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereLabel($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereLog($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereRecords($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereRejected($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereRunAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereRunBy($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereStatus($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereType($value)
 * @method static \Illuminate\Database\Eloquent\Builder|Import whereUpdatedAt($value)
 * @mixin \Eloquent
 */
class Import extends Base
{
    public const STATUS_PENDING = 0;
    public const STATUS_COMPLETE = 1;
    public const STATUS_PROCESSING = 2;

    /**
     * The import statuses.
     *
     * @var array
     */
    protected $statuses = [
        self::STATUS_PENDING => 'Pending',
        self::STATUS_PROCESSING => 'Processing',
        self::STATUS_COMPLETE => 'Complete',
    ];

    /**
     * The table name.
     * @var string
     */
    protected $table = 'core_import';

    /**
     * The attributes which should be cast to specific types during hydration.
     *
     * @var array
     */
    protected $casts = [
        'records' => 'integer',
        'run_at' => 'datetime',
    ];

    /**
     * @var Logger
     */
    protected $logger;

    /**
     * Boot the entity domain logic.
     */
    public static function boot()
    {
        parent::boot();

        static::saving(function ($model) {
            if (! isset($model->created_by)) {
                if ($authUser = auth()->user()) {
                    $model->created_by = $authUser->getAuthIdentifier();
                } elseif ($adminUser = app(UserRepository::class)->findAdminUser()) {
                    $model->created_by = $adminUser->getKey();
                } else {
                    // Assuming IT Staff admin user is ID 1.
                    $model->created_by = 1;
                }
            }
        });
    }

    /**
     * Determine if the import is pending.
     *
     * @return bool
     */
    public function isPending()
    {
        return $this->status === static::STATUS_PENDING;
    }

    /**
     * Determine if the import is completed.
     *
     * @return bool
     */
    public function isProcessing()
    {
        return $this->status === static::STATUS_PROCESSING;
    }

    /**
     * Determine if the import is completed.
     *
     * @return bool
     */
    public function isComplete()
    {
        return $this->status === static::STATUS_COMPLETE;
    }

    /**
     * The human friendly label for the status.
     *
     * @return string
     */
    public function getStatusLabel()
    {
        $colors = [
            static::STATUS_PENDING => 'danger',
            static::STATUS_PROCESSING => 'yellow',
            static::STATUS_COMPLETE => 'green',
        ];

        return new HtmlString(sprintf(
            '<span class="fa fa-circle text-%s"></span> %s',
            Arr::get($colors, $this->status, 'muted'),
            $this->statuses[$this->status]
        ));
    }

    /**
     * Display a friendly label for the import type.
     *
     * @return string
     */
    public function getTypeLabel()
    {
        return $this->label;
    }

    /**
     * Resolve the type class out of the IoC container and pass along this instance.
     *
     * @param array $params
     * @return \Ignite\Core\Contracts\Importable
     */
    public function resolveType($params = [])
    {
        return app()->make($this->type, $params)->record($this);
    }

    /**
     * The filesystem manager for the disk.
     *
     * @return \Illuminate\Filesystem\FilesystemManager
     */
    public function disk()
    {
        return Storage::disk($this->disk);
    }

    /**
     * Determine whether the file exists for this import.
     *
     * @return bool
     */
    public function hasFile()
    {
        return $this->disk()->exists($this->file);
    }

    /**
     * Get the file data contents from storage.
     *
     * @return string
     */
    public function getFileData()
    {
        return $this->disk()->get($this->file);
    }

    /**
     * Get the file as a stream from storage.
     *
     * @return resource
     */
    public function getFileStream()
    {
        return $this->disk()->getDriver()->readStream($this->file);
    }

    /**
     * The full path to the import file in storage.
     *
     * @return string
     */
    public function getFilePath()
    {
        return $this->disk()->path($this->file);
    }

    /**
     * Delete the csv file from storage.
     *
     * @return bool
     */
    public function deleteFile()
    {
        return $this->disk()->delete($this->file);
    }

    /**
     * Determine whether the log file exists for this import.
     *
     * @return bool
     */
    public function hasLog()
    {
        return $this->disk()->exists($this->log);
    }

    /**
     * Get the log data contents from storage.
     *
     * @return string
     */
    public function getLogData()
    {
        return $this->disk()->get($this->log);
    }

    /**
     * Get the log as a stream from storage.
     *
     * @return resource
     */
    public function getLogStream()
    {
        return $this->disk()->getDriver()->readStream($this->log);
    }

    /**
     * The full path to the log file in storage.
     *
     * @return string
     */
    public function getLogPath()
    {
        return $this->disk()->path($this->log);
    }

    /**
     * Get the log formatted for display.
     *
     * @param int $perPage
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function getFormattedLog($perPage = 100)
    {
        if (! $this->hasLog()) {
            return new LengthAwarePaginator([], 0, 1);
        }

        $formatter = app(LogFormatter::class, ['import' => $this]);

        $items = collect($formatter->format());

        return app(LogPaginator::class)->paginate($items, $perPage);
    }

    /**
     * Get the log instance.
     *
     * @param int $level
     * @return Logger
     */
    public function getLogger($level = Logger::DEBUG)
    {
        if (is_null($this->logger)) {
            $this->logger = app(Logger::class, ['name' => 'import']);
            $this->logger->pushHandler(
                app(StreamHandler::class, [
                    'stream' => $this->getLogPath(),
                    'level' => $level,
                ])
            );
        }

        return $this->logger;
    }

    /**
     * Delete the log file from storage.
     *
     * @return bool
     */
    public function deleteLog()
    {
        return $this->disk()->delete($this->log);
    }

    /**
     * The relationship to the user that created the import.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function createdBy()
    {
        return $this->belongsTo(User::class, 'created_by', 'user_id');
    }

    /**
     * The relationship to the user that created the import.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function runBy()
    {
        return $this->belongsTo(User::class, 'run_by', 'user_id');
    }

    /**
     * The user that initially created the imported data.
     *
     * @return string
     */
    public function getCreatedBy()
    {
        return optional($this->createdBy)->fullName() ?? 'Unknown';
    }

    /**
     * The date the import was initially created.
     *
     * @return string
     */
    public function getCreatedAt()
    {
        return $this->created_at->format('F j, Y g:ia');
    }

    /**
     * The user that last imported data.
     *
     * @return string
     */
    public function getRunBy()
    {
        return optional($this->runBy)->fullName() ?? 'Nobody';
    }

    /**
     * The date of the last import run.
     *
     * @return string
     */
    public function getRunAt()
    {
        return optional($this->run_at)->format('F j, Y g:ia') ?? 'Never';
    }

    /**
     * Cast the data to an array of user presentable values.
     *
     * @return array
     */
    public function toPresentableArray()
    {
        return [
            'id' => $this->getKey(),
            'type' => $this->getTypeLabel(),
            'file' => $this->file,
            'log' => $this->log,
            'disk' => $this->disk,
            'records' => $this->records,
            'imported' => $this->imported,
            'rejected' => $this->rejected,
            'created_by' => $this->getCreatedBy(),
            'created_at' => $this->getCreatedAt(),
            'run_by' => $this->getRunBy(),
            'run_at' => $this->getRunAt(),
            'status' => $this->getStatusLabel(),
        ];
    }
}
