<?php

declare(strict_types=1);

namespace App\Application\Actions;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Twig\Environment;
use PDO;
use App\Application\Services\BlogProvider;

class BlogPostAction
{
    private Environment $twig;
    private PDO $pdo;
    private BlogProvider $blogProvider;

    public function __construct(Environment $twig, PDO $pdo, BlogProvider $blogProvider)
    {
        $this->twig = $twig;
        $this->pdo = $pdo;
        $this->blogProvider = $blogProvider;
    }

    public function __invoke(Request $request, Response $response, array $args): Response
    {
        $start = microtime(true);

        $stub = $args['stub'];
        $article = $this->blogProvider->getByStub($stub);

        if (!$article) {
            $html = $this->twig->render('404.twig');
            $response->getBody()->write($html);

            // log timing for failed request as well
            try {
                $end = microtime(true);
                $us = (int)(($end - $start) * 1_000_000);
                $url = (string)$request->getUri();
                $logPath = dirname(__DIR__, 3) . '/logs/times.log';
                @file_put_contents($logPath, sprintf("%d %s\n", $us, $url), FILE_APPEND | LOCK_EX);
                // blog visit logging for not found post
                try {
                    $this->logBlogVisit($request);
                } catch (\Throwable $e) {
                    // ignore logging errors
                }
            } catch (\Throwable $e) {
                // ignore logging errors
            }

            return $response->withStatus(404)->withHeader('Content-Type', 'text/html');
        }

        $stmt = $this->pdo->prepare('SELECT * FROM contents WHERE key = :key ORDER BY id DESC LIMIT 1');
        $stmt->execute([':key' => 'links']);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        $links = $row ? json_decode($row['content'], true) : [];

        $alsoReading = $this->blogProvider->getRandomArticles(3);

        $html = $this->twig->render('blog/post.twig', ['article' => $article, 'links' => $links, 'alsoReading' => $alsoReading]);
        $response->getBody()->write($html);

        // timing end and log
        try {
            $end = microtime(true);
            $us = (int)(($end - $start) * 1_000_000);
            $url = (string)$request->getUri();
            $logPath = dirname(__DIR__, 3) . '/logs/times.log';
            @file_put_contents($logPath, sprintf("%d %s\n", $us, $url), FILE_APPEND | LOCK_EX);
            // blog visit logging for successful post
            try {
                $this->logBlogVisit($request);
            } catch (\Throwable $e) {
                // ignore logging errors
            }
        } catch (\Throwable $e) {
            // ignore logging errors
        }

        return $response->withHeader('Content-Type', 'text/html');
    }

    private function logBlogVisit(Request $request): void
    {
        $server = $request->getServerParams();
        $xff = $request->getHeaderLine('X-Forwarded-For');
        $ip = trim(explode(',', $xff)[0] ?? ($server['REMOTE_ADDR'] ?? '')) ?: 'unknown';
        $userAgent = $request->getHeaderLine('User-Agent') ?: '';
        $url = (string)$request->getUri();

        $logDir = dirname(__DIR__, 3) . '/logs';
        $detailPath = $logDir . '/blog_detail.log';

        @file_put_contents($detailPath, sprintf("%s\t%s\t%s\n", date('Y-m-d H:i:s'), $ip, $url), FILE_APPEND | LOCK_EX);

        $now = time();
        $this->pdo->beginTransaction();
        try {
            $this->pdo->exec("CREATE TABLE IF NOT EXISTS blog_visits (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                ip TEXT,
                useragent TEXT,
                cnt INTEGER DEFAULT 0,
                first_seen INTEGER,
                last_seen INTEGER,
                UNIQUE(ip, useragent)
            )");

            $select = $this->pdo->prepare('SELECT id, cnt FROM blog_visits WHERE ip = :ip AND useragent = :ua');
            $select->execute([':ip' => $ip, ':ua' => $userAgent]);
            $row = $select->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                $update = $this->pdo->prepare('UPDATE blog_visits SET cnt = cnt + 1, last_seen = :last WHERE id = :id');
                $update->execute([':last' => $now, ':id' => $row['id']]);
            } else {
                $insert = $this->pdo->prepare('INSERT INTO blog_visits (ip, useragent, cnt, first_seen, last_seen) VALUES (:ip, :ua, 1, :t, :t)');
                $insert->execute([':ip' => $ip, ':ua' => $userAgent, ':t' => $now]);
            }

            $this->pdo->commit();
        } catch (\Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
}
