<?php
namespace Misuzu\AuditLog;

use InvalidArgumentException;
use Index\Data\DbStatementCache;
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Index\Net\IPAddress;
use Misuzu\Pagination;
use Misuzu\Users\UserInfo;

class AuditLog {
    private DbStatementCache $cache;

    public function __construct(IDbConnection $dbConn) {
        $this->cache = new DbStatementCache($dbConn);
    }

    public function countLogs(
        UserInfo|string|null $userInfo = null,
        IPAddress|string|null $remoteAddr = null
    ): int {
        if($userInfo instanceof UserInfo)
            $userInfo = $userInfo->getId();
        if($remoteAddr instanceof IPAddress)
            $remoteAddr = (string)$remoteAddr;

        $hasUserInfo = $userInfo !== null;
        $hasRemoteAddr = $remoteAddr !== null;

        $args = 0;
        $query = 'SELECT COUNT(*) FROM msz_audit_log';
        if($hasUserInfo) {
            ++$args;
            $query .= ' WHERE user_id = ?';
        }
        if($hasRemoteAddr) {
            $query .= (++$args > 1 ? ' AND' : ' WHERE');
            $query .= ' log_ip = INET6_ATON(?)';
        }

        $stmt = $this->cache->get($query);

        $args = 0;
        if($hasUserInfo)
            $stmt->addParameter(++$args, $userInfo);
        if($hasRemoteAddr)
            $stmt->addParameter(++$args, $remoteAddr);

        $stmt->execute();
        $result = $stmt->getResult();
        $count = 0;

        if($result->next())
            $count = $result->getInteger(0);

        return $count;
    }

    public function getLogs(
        UserInfo|string|null $userInfo = null,
        IPAddress|string|null $remoteAddr = null,
        ?Pagination $pagination = null
    ): array {
        if($userInfo instanceof UserInfo)
            $userInfo = $userInfo->getId();
        if($remoteAddr instanceof IPAddress)
            $remoteAddr = (string)$remoteAddr;

        $hasUserInfo = $userInfo !== null;
        $hasRemoteAddr = $remoteAddr !== null;
        $hasPagination = $pagination !== null;

        $args = 0;
        $query = 'SELECT user_id, log_action, log_params, UNIX_TIMESTAMP(log_created), INET6_NTOA(log_ip), log_country FROM msz_audit_log';
        if($hasUserInfo) {
            ++$args;
            $query .= ' WHERE user_id = ?';
        }
        if($hasRemoteAddr) {
            $query .= (++$args > 1 ? ' AND' : ' WHERE');
            $query .= ' log_ip = INET6_ATON(?)';
        }
        $query .= ' ORDER BY log_created DESC';
        if($hasPagination)
            $query .= ' LIMIT ? OFFSET ?';

        $stmt = $this->cache->get($query);

        $args = 0;
        if($hasUserInfo)
            $stmt->addParameter(++$args, $userInfo);
        if($hasRemoteAddr)
            $stmt->addParameter(++$args, $remoteAddr);
        if($hasPagination) {
            $stmt->addParameter(++$args, $pagination->getRange());
            $stmt->addParameter(++$args, $pagination->getOffset());
        }

        $stmt->execute();
        $result = $stmt->getResult();
        $logs = [];

        while($result->next())
            $logs[] = new AuditLogInfo($result);

        return $logs;
    }

    public function createLog(
        UserInfo|string|null $userInfo,
        string $action,
        array $params = [],
        IPAddress|string $remoteAddr = '::1',
        string $countryCode = 'XX'
    ): void {
        if($userInfo instanceof UserInfo)
            $userInfo = $userInfo->getId();
        if($remoteAddr instanceof IPAddress)
            $remoteAddr = (string)$remoteAddr;

        // action names should have stricter validation,
        // i do want to switch to a lowercase colon separated format later but i'll save that for the unified log in Hanyuu
        $actionTrim = trim($action);
        if($actionTrim !== $action || empty($actionTrim))
            throw new InvalidArgumentException('$action may not be empty.');

        if(strlen($countryCode) !== 2 || !ctype_alpha($countryCode))
            throw new InvalidArgumentException('$countryCode must be two alpha characters.');

        foreach($params as &$param) {
            if(is_array($param))
                $param = implode(', ', $param);
            elseif(is_object($param))
                $param = (string)$param;
        }

        $params = json_encode($params);

        $stmt = $this->cache->get('INSERT INTO msz_audit_log (user_id, log_action, log_params, log_ip, log_country) VALUES (?, ?, ?, INET6_ATON(?), UPPER(?))');
        $stmt->addParameter(1, $userInfo);
        $stmt->addParameter(2, $action);
        $stmt->addParameter(3, $params);
        $stmt->addParameter(4, $remoteAddr);
        $stmt->addParameter(5, $countryCode);
        $stmt->execute();
    }
}
