<?php

namespace craft\feedme\services;

use Craft;
use craft\base\Component;
use craft\db\Query;
use craft\feedme\Plugin;
use craft\helpers\App;
use craft\helpers\Db;
use craft\helpers\Json;
use Exception;
use Illuminate\Support\Collection;
use yii\base\InvalidArgumentException;
use yii\log\DbTarget;
use yii\log\Logger;

class Logs extends Component
{
    // Properties
    // =========================================================================

    /**
     * @var bool
     */
    public bool $enableRotation = true;

    /**
     * @var int
     */
    public int $maxFileSize = 6656; // 6.5MB limit for support

    /**
     * @var int
     */
    public int $maxLogFiles = 20;

    /**
     * @var
     */
    public mixed $fileMode = null;

    /**
     * @var int
     */
    public int $dirMode = 0775;

    /**
     * @var bool
     */
    public bool $rotateByCopy = true;

    /**
     * @var
     */
    public mixed $logFile = null;

    public const LOG_CATEGORY = 'feed-me';
    public const LOG_TABLE = '{{%feedme_logs}}';
    public const LOG_TABLE_LIMIT = 300;

    public const LOG_LEVEL_MAP = [
        Logger::LEVEL_ERROR => 'error',
        Logger::LEVEL_WARNING => 'warning',
        Logger::LEVEL_INFO => 'info',
        Logger::LEVEL_TRACE => 'trace',
        Logger::LEVEL_PROFILE => 'profile',
    ];

    // Public Methods
    // =========================================================================

    /**
     * @inheritDoc
     */
    public function init(): void
    {
        Craft::$app->getLog()->targets['feed-me'] = Craft::createObject([
            'class' => DbTarget::class,
            'logTable' => self::LOG_TABLE,
            'levels' => $this->getLogLevels(),
            'enabled' => $this->isEnabled(),
            'categories' => [self::LOG_CATEGORY],
            'logVars' => [],
            'prefix' => static function(array $message) {
                $log = Json::decodeIfJson($message[0]);
                $feed = $log['feed'] ?? null;
                return $feed ? "[$feed]" : '';
            },
        ]);
    }

    /**
     * @param $method
     * @param $message
     * @param array $params
     * @param array $options
     * @throws Exception
     */
    public function log($method, $message, array $params = [], array $options = []): void
    {
        $level = explode('::', $method)[1];
        $message = Craft::t('feed-me', $message, $params);

        $json = [
            'message' => $message,
            'feed' => Plugin::$feedName,
            'key' => $options['key'] ?? Plugin::$stepKey,
        ];

        Craft::getLogger()->log(
            Json::encode($json),
            self::logLevelInt($level),
            self::LOG_CATEGORY,
        );
    }

    /**
     *
     */
    public function clear(): void
    {
        Craft::$app->getDb()->createCommand()
            ->truncateTable(self::LOG_TABLE)
            ->execute();
    }

    public function prune(): void
    {
        $ids = (new Query())
            ->select(['id'])
            ->from(self::LOG_TABLE)
            ->orderBy(['log_time' => SORT_DESC])
            ->limit(self::LOG_TABLE_LIMIT)
            ->column();

        if (count($ids)) {
            Craft::$app->getDb()->createCommand()
                ->delete(self::LOG_TABLE, ['not', ['in', 'id', $ids]])
                ->execute();
        }
    }

    /**
     * @param null $type
     * @return array
     * @throws \yii\base\Exception
     */
    public function getLogEntries($type = null): array
    {
        $query = (new Query())
            ->select('*')
            ->where(['category' => self::LOG_CATEGORY])
            ->orderBy(['log_time' => SORT_DESC])
            ->from(self::LOG_TABLE)

            // Hard-coded until pagination is implemented
            ->limit(self::LOG_TABLE_LIMIT);

        if ($type) {
            $query->andWhere(['level' => self::logLevelInt($type)]);
        }

        $logEntries = $query->collect()->reduce(function(Collection $logs, array $row) {
            $json = Json::decodeIfJson($row['message']);
            $key = $json['key'] ?? $logs->count();

            if ($json['message']) {
                $message = $json['feed'] ? $json['feed'] . ': ' . $json['message'] : $json['message'];
            } else {
                $message = $json;
            }
            $log = [
                'type' => self::logLevelName($row['level']),
                'date' => Db::prepareDateForDb($row['log_time']),
                'message' => $message,
                'key' => $key,
            ];

            if ($logs->has($key)) {
                $parentLog = $logs->get($key);
                $parentLog['items'][] = $log;
                $logs->put($key, $parentLog);
            } else {
                $logs->put($key, $log);
            }

            return $logs;
        }, Collection::make());

        return $logEntries->all();
    }

    // Private Methods
    // =========================================================================

    private function isEnabled(): bool
    {
        $config = Plugin::$plugin->service->getConfig('logging');

        return App::parseBooleanEnv($config) ?? true;
    }

    private function getLogLevels(): array
    {
        $config = Plugin::$plugin->service->getConfig('logging');

        return match ($config) {
            'error' => ['error'],
            default => [],
        };
    }

    private function logLevelInt(string $level): int
    {
        return match ($level) {
            'error' => Logger::LEVEL_ERROR,
            'warning' => Logger::LEVEL_WARNING,
            'info' => Logger::LEVEL_INFO,
            'trace' => Logger::LEVEL_TRACE,
            'profile' => Logger::LEVEL_PROFILE,
        };
    }

    private static function logLevelName(int $level): string
    {
        $level = self::LOG_LEVEL_MAP[$level] ?? null;

        if ($level === null) {
            throw new InvalidArgumentException("Invalid log level: $level");
        }

        return $level;
    }
}
