<?php
require_once 'DatabaseInitializer.php';
require_once 'ArticleManagerDAO.php';
require_once 'Article.php';
/**
 * Klasse: Eine SQLLite3 Lösung des ArticleManagerDAO.
 *
 * @author Niklas Ortmann
 */
class DatabaseArticleManager implements ArticleManagerDAO {

    private static $instance = null;
    private $dbPath;

    /**
     * Konstruktor
     *
     * Erstellt die Datenbankverbindung und die Tabelle articles.
     * @throws RuntimeException
     */
    public function __construct()
    {
        $this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
        DatabaseInitializer::initialize($this->dbPath);
    }

    /**
     * Baut die Verbindung zur Datenbank auf.
     *
     * @return PDO Datenbankverbindung
     * @throws RuntimeException
     */
    private function getConnection()
    {
        try {
            $db = new PDO('sqlite:' . $this->dbPath, null, null);
            $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $db->exec('PRAGMA foreign_keys = ON;');
            return $db;
        } catch (PDOException $e) {
            throw new RuntimeException("internal_error");
        }
    }

    /**
     * Gibt die DatabaseArticleManager-Instanz zurück.
     * @return DatabaseArticleManager
     */
    public static function getInstance()
    {
        if (self::$instance == null) {
            self::$instance = new DatabaseArticleManager();
        }
        return self::$instance;
    }

    public function addArticle($title, $content, $author, $category, $tags)
    {
        try {
            $db = $this->getConnection();

            $sql = "INSERT INTO articles (title, content, author, category, tags) 
                    VALUES (:title, :content, :author, :category, :tags);";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            // Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern
            $success = $command->execute([
                ":title"    => $title,
                ":content"  => $content,
                ":author"   => $author,
                ":category" => $category,
                ":tags"     => $tags
            ]);

            if (!$success) {
                throw new InternalServerErrorException("internal_error");
            }

            return intval($db->lastInsertId());

        } catch (PDOException $e) {
            throw new InternalServerErrorException($e->getMessage());
        }
    }

    public function updateArticle($id, $article, $author)
    {
        if (empty($article)) {
            throw new InternalServerErrorException("internal_error");
        }

        // Berechtigungsprüfung analog zur lokalen Implementierung:
        if ($article->getAuthor() !== $author) {
            throw new UnauthorizedAccessException("unauthorized_access");
        }

        try {
            $db = $this->getConnection();

            $sql = "UPDATE articles 
                    SET title = :title, content = :content, author = :author, category = :category, tags = :tags 
                    WHERE id = :id;";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            $success = $command->execute([
                ":id"       => $id,
                ":title"    => $article->getTitle(),
                ":content"  => $article->getContent(),
                ":author"   => $author,
                ":category" => $article->getCategory(),
                ":tags"     => $article->getTags()
            ]);

            // rowCount() prüft, ob eine Zeile mit dieser ID existierte und geändert werden konnte
            if (!$success || $command->rowCount() === 0) {
                // Falls die ID nicht existiert, prüfen wir, ob sie überhaupt da ist
                if (!$this->getArticle($id)) {
                    throw new NotFoundException("missing_id");
                }
            }
        } catch (PDOException $e) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    public function deleteArticle($id, $author)
    {
        $article = $this->getArticle($id);
        if (empty($article)) {
            throw new NotFoundException("not_found_article");
        }

        // Berechtigungsprüfung:
        if ($article->getAuthor() !== $author) {
            throw new UnauthorizedAccessException("unauthorized_access");
        }

        try {
            $db = $this->getConnection();
            $sql = "DELETE FROM articles WHERE id = :id;";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            if (!$command->execute([":id" => $id])) {
                throw new InternalServerErrorException("internal_error");
            }
        } catch (PDOException $exc) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    public function getArticle($id)
    {
        try {
            $db = $this->getConnection();
            $sql = "SELECT * FROM articles WHERE id = :id;";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            $command->execute([":id" => $id]);
            $row = $command->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                $likes = $this->getLikesForArticle(intval($row['id']));

                return new Article(
                    intval($row['id']),
                    $row['title'],
                    $row['content'],
                    $row['author'],
                    $row['category'],
                    $row['tags'],
                    $row['created'],
                    $likes
                );
            }

            return null;
        } catch (PDOException $e) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    public function getAllArticles()
    {
        try {
            $db = $this->getConnection();
            $sql = "SELECT * FROM articles;";

            $command = $db->query($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            $rows = $command->fetchAll(PDO::FETCH_ASSOC);
            $articles = [];

            foreach ($rows as $row) {
                $articles[] = new Article(
                    intval($row['id']),
                    $row['title'],
                    $row['content'],
                    $row['author'],
                    $row['category'],
                    $row['tags'],
                    $row['created']
                );
            }

            return $articles;
        } catch (PDOException $e) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    public function getArticlesByAuthor($author)
    {
        try {
            $db = $this->getConnection();
            $sql = "SELECT * FROM articles WHERE author = :author;";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            $command->execute([":author" => $author]);
            $rows = $command->fetchAll(PDO::FETCH_ASSOC);
            $filteredArticles = [];

            foreach ($rows as $row) {
                $likes = $this->getLikesForArticle(intval($row['id']));

                $filteredArticles[] = new Article(
                    intval($row['id']),
                    $row['title'],
                    $row['content'],
                    $row['author'],
                    $row['category'],
                    $row['tags'],
                    $row['created'],
                    $likes
                );
            }

            return $filteredArticles;
        } catch (PDOException $e) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    public function getArticlesByCategory($category)
    {
        try {
            $db = $this->getConnection();
            $sql = "SELECT * FROM articles WHERE category = :category;";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            $command->execute([":category" => $category]);
            $rows = $command->fetchAll(PDO::FETCH_ASSOC);
            $filteredArticles = [];

            foreach ($rows as $row) {
                $likes = $this->getLikesForArticle(intval($row['id']));

                $filteredArticles[] = new Article(
                    intval($row['id']),
                    $row['title'],
                    $row['content'],
                    $row['author'],
                    $row['category'],
                    $row['tags'],
                    $row['created'],
                    $likes
                );
            }

            return $filteredArticles;
        } catch (PDOException $exc) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    public function search(string $keyword): array
    {
        $cleankeyword = trim($keyword);

        // Leeren Suchbegriff sofort abfangen
        if ($cleankeyword === '') {
            return [];
        }

        try {
            $db = $this->getConnection();

            $sql = "SELECT id, title, content, author, category, tags, created 
                    FROM articles 
                    WHERE title LIKE :keyword 
                       OR content LIKE :keyword
                       OR tags LIKE :keyword";

            $command = $db->prepare($sql);
            if (!$command) {
                throw new InternalServerErrorException("internal_error");
            }

            // Wildcards für die Suche hinzufügen
            $searchParam = '%' . $cleankeyword . '%';

            $success = $command->execute([
                ":keyword" => $searchParam
            ]);

            if (!$success) {
                throw new InternalServerErrorException("internal_error");
            }

            $rows = $command->fetchAll(PDO::FETCH_ASSOC);
            $filteredArticles = [];

            foreach ($rows as $row) {
                $likes = $this->getLikesForArticle(intval($row['id']));

                $filteredArticles[] = new Article(
                    intval($row['id']),
                    $row['title'] ?? '',
                    $row['content'] ?? '',
                    $row['author'] ?? '',
                    $row['category'] ?? '',
                    $row['tags'] ?? '',
                    $row['created'] ?? '',
                    $likes
                );
            }

            return $filteredArticles;

        } catch (PDOException $e) {
            throw new InternalServerErrorException("internal_error");
        }
    }

    /**
     * Holt alle User-IDs, die einen bestimmten Beitrag geliked haben.
     *
     * @return String[] UserIDs
     * @throws InternalServerErrorException
     */
    private function getLikesForArticle(int $articleId): array
    {
        try {
            $db = $this->getConnection();
            $sql = "SELECT user_id FROM likes WHERE article_id = :article_id;";
            $command = $db->prepare($sql);
            $command->execute([':article_id' => $articleId]);

            return $command->fetchAll(PDO::FETCH_COLUMN) ?: [];
        } catch (PDOException $e) {
            return [];
        }
    }

    public function toggleLike(int $articleId, string $userId): bool
    {
        // prüfen, ob der Artikel überhaupt existiert
        $article = $this->getArticle($articleId);
        if (!$article) {
            throw new NotFoundException("missing_id");
        }

        try {
            $db = $this->getConnection();

            // prüfen, ob das Like bereits existiert
            $checkSql = "SELECT COUNT(*) FROM likes WHERE article_id = :article_id AND user_id = :user_id;";
            $checkCommand = $db->prepare($checkSql);
            $checkCommand->execute([
                ':article_id' => $articleId,
                ':user_id'    => $userId
            ]);

            $hasLiked = (int)$checkCommand->fetchColumn() > 0;

            if ($hasLiked) {
                // wenn bereits geliked -> Unlike
                $deleteSql = "DELETE FROM likes WHERE article_id = :article_id AND user_id = :user_id;";
                $deleteCommand = $db->prepare($deleteSql);
                $deleteCommand->execute([
                    ':article_id' => $articleId,
                    ':user_id'    => $userId
                ]);
                return false; // gibt false zurück, da der Beitrag jetzt nicht mehr geliked ist
            } else {
                // wenn noch nicht geliked -> Like
                $insertSql = "INSERT INTO likes (article_id, user_id) VALUES (:article_id, :user_id);";
                $insertCommand = $db->prepare($insertSql);
                $insertCommand->execute([
                    ':article_id' => $articleId,
                    ':user_id'    => $userId
                ]);
                return true; // gibt true zurück, da der Beitrag jetzt geliked ist
            }

        } catch (PDOException $e) {
            throw new InternalServerErrorException("internal_error");
        }
    }
}