<?php

namespace App\Ardent\LoggerBundle\Service;

use App\Ardent\LoggerBundle\Entity\LogEntry;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\InvalidArgumentException;

class LoggerService
{
    /**
     * Detailed debug information.
     */
    const DEBUG = 100;

    /**
     * Interesting events.
     *
     * Examples: User logs in, SQL logs.
     */
    const INFO = 200;

    /**
     * Uncommon events.
     */
    const NOTICE = 250;

    /**
     * Exceptional occurrences that are not errors.
     *
     * Examples: Use of deprecated APIs, poor use of an API,
     * undesirable things that are not necessarily wrong.
     */
    const WARNING = 300;

    /**
     * Runtime errors.
     */
    const ERROR = 400;

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     */
    const CRITICAL = 500;

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc.
     * This should trigger the SMS alerts and wake you up.
     */
    const ALERT = 550;

    /**
     * Urgent alert.
     */
    const EMERGENCY = 600;

    /**
     * Logging levels from syslog protocol defined in RFC 5424.
     *
     * @var array Logging levels
     */
    protected static $levels = array(
        self::DEBUG => 'DEBUG',
        self::INFO => 'INFO',
        self::NOTICE => 'NOTICE',
        self::WARNING => 'WARNING',
        self::ERROR => 'ERROR',
        self::CRITICAL => 'CRITICAL',
        self::ALERT => 'ALERT',
        self::EMERGENCY => 'EMERGENCY',
    );

    private $name;

    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * LoggerService constructor.
     *
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->name = '';
        $this->em = $em;
    }

    public function addNotice($message, $context = [])
    {
        return $this->addRecord(self::NOTICE, $message, $context);
    }

    public function addWarning($message, $context = [])
    {
        return $this->addRecord(self::WARNING, $message, $context);
    }

    public function addError($message, $context = [])
    {
        return $this->addRecord(self::ERROR, $message, $context);
    }

    /**
     * Adds a log record.
     *
     * @param int    $level   The logging level
     * @param string $message The log message
     * @param array  $context The log context
     *
     * @return bool Whether the record has been processed
     */
    public function addRecord($level, $message, $context = [])
    {
        $levelName = static::getLevelName($level);

        if ($level > self::WARNING) {
            $context = array_merge($context, $this->automaticContext());
        }

        $logEntry = (new LogEntry())
            ->setMessage((string) $message)
            ->setContext($context)
            ->setLevel($level)
            ->setLevelName($levelName)
            ->setChannel($this->name)
        ;

        $this->em->persist($logEntry);
        $this->em->flush();

        return $logEntry->getId();
    }

    private function automaticContext()
    {
        // filter the kernel classes and itself
        $classFilters = [
            'Ardent\LoggerBundle\Service\LoggerService',
            'HttpKernel\Kernel',
            'HttpKernel\HttpKernel',
        ];
        $backtraces = debug_backtrace();
        $context = [];
        $traces = 0;
        foreach ($backtraces as $trace) {
            $skip = false;
            // Applying the filters, $skip if match is found
            foreach ($classFilters as $class) {
                if (strpos($trace['class'], $class)) {
                    $skip = true;
                }
            }
            if ($skip) {
                continue;
            }
            // Save the function and class of this level
            $context[] = "function[$traces]: ".$trace['function'];
            $context[] = "class[$traces]: ".$trace['class'];
            ++$traces;
        }

        return $context;
    }

    /**
     * Get the latest log entry limited by $limit and of at least log level $level.
     *
     * @param int $limit The maximum amount of entries
     * @param int $level The minimum log level for the entries
     *
     * @return mixed
     */
    public function getLatest($limit = 1, $level = self::DEBUG)
    {
        $qb = $this->em->createQueryBuilder();

        $qb
            ->select('l')
            ->from('ArdentLoggerBundle:LogEntry', 'l')
            ->where($qb->expr()->gte('l.level', '?1'))
            ->orderBy('l.createdAt', 'DESC')
            ->setParameter(1, $level)
            ->setMaxResults($limit);

        return $qb->getQuery()->getResult();
    }

    /**
     * Gets the name of the logging level.
     *
     * @param int $level
     *
     * @return string
     */
    public static function getLevelName($level)
    {
        if (!isset(static::$levels[$level])) {
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels)));
        }

        return static::$levels[$level];
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     *
     * @return LoggerService
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }
}