<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Logging;

use Monolog\Formatter\JsonFormatter;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\GroupHandler;
use Monolog\Handler\HandlerInterface;
use Monolog\Handler\NullHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Handler\StreamHandler;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Utility\Environment;
use Psr\Log\LoggerInterface;

/**
 * @Flow\Scope("singleton")
 */
class LoggerFactory
{

    /**
     * @var Environment
     * @Flow\Inject()
     */
    protected $environment;

    /**
     * @var array
     * @Flow\InjectConfiguration(path="logging")
     */
    protected $settings;

    /** @var LoggerInterface */
    private $logger;

    /** @var \Closure[] */
    private $nameSetters = [];

    public function getLogger(): LoggerInterface
    {
        if ($this->logger === null) {
            $this->logger = $this->initializeLogger();
        }
        return $this->logger;
    }

    private function initializeLogger(): LoggerInterface
    {
        $handlers = [
            $this->getConsoleOutput(),
            $this->getFileOutput(),
            $this->getSlackHandler(),
            $this->getJsonFileOutput(),
        ];

        $logger = new Logger($this->name(), [ new GroupHandler($handlers), ]);
        $this->nameSetters[] = function (string $name) use ($logger) {
            $logger->setName($name);
        };
        return $logger;
    }

    private function getSlackHandler(): HandlerInterface
    {
        $explicitlyEnabledInConfiguration = $this->settings['slack']['enable'] ?? null;
        $enabled = \is_bool($explicitlyEnabledInConfiguration)
            ? $explicitlyEnabledInConfiguration
            : $this->environment->getContext()->isProduction();

        if (!$enabled) {
            return new NullHandler();
        }

        $slackHandler = new SlackWebhookHandler(
            $this->settings['slack']['webhookUrl'],
            $this->settings['slack']['channel'] ?? null,
            $this->name(),
            true,
            null,
            true,
            false,
            Logger::WARNING
        );
        $this->nameSetters[] = function (string $name) use ($slackHandler) {
            $slackHandler->getSlackRecord()->setUsername($name);
        };

        return new SummaryHandler(
            Logger::toMonologLevel($this->settings['slack']['levels']['summary']) ?? Logger::WARNING,
            $slackHandler,
            Logger::toMonologLevel($this->settings['slack']['levels']['trigger']) ?? Logger::ERROR
        );
    }

    private function getConsoleOutput(): HandlerInterface
    {
        $handler = new StreamHandler('php://stdout', Logger::INFO);

        // Prepending line break before each line to counteract symfony progressbars
        $formatter = new LineFormatter("\n" . LineFormatter::SIMPLE_FORMAT);
        $formatter->setJsonPrettyPrint(true);
        $formatter->allowInlineLineBreaks(true);
        $formatter->includeStacktraces(false);
        $handler->setFormatter($formatter);

        return $handler;
    }

    private function getFileOutput(): RotatingFileHandler
    {
        $directory = \defined('FLOW_PATH_DATA') ? FLOW_PATH_DATA : sys_get_temp_dir();
        $handler = new RotatingFileHandler($directory . '/Newland.Toubiz.Sync.Neos.log', 7);

        $formatter = new LineFormatter();
        $formatter->setJsonPrettyPrint(true);
        $formatter->allowInlineLineBreaks(true);
        $formatter->includeStacktraces(true);
        $handler->setFormatter($formatter);

        return $handler;
    }

    private function getJsonFileOutput(): RotatingFileHandler
    {
        $directory = \defined('FLOW_PATH_DATA') ? FLOW_PATH_DATA : sys_get_temp_dir();
        $handler = new RotatingFileHandler($directory . '/Newland.Toubiz.Sync.Neos.log.json', 7);

        $formatter = new SingleLineJsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES);
        $handler->setFormatter($formatter);

        return $handler;
    }

    private function name(): string
    {
        if (array_key_exists('name', $this->settings)) {
            return $this->settings['name'];
        }

        // Maxcluster support for projects in /var/www/share/ABC-123
        if (\Safe\preg_match('/\/var\/www\/share\/([A-Z]+-\d+)\/.*/', __DIR__, $matches)) {
            return $matches[1];
        }

        return 'Newland.Toubiz.Sync.Neos';
    }

    public function updateName(string $name): void
    {
        $name = sprintf('%s [%s]', $this->name(), $name);
        foreach ($this->nameSetters as $setter) {
            $setter($name);
        }
    }
}
