<?php

namespace Khofmann\Models\Post;

use Api\User\User as UserUser;
use DateTime;
use Exception;
use Khofmann\Models\User\User;
use JsonSerializable;
use Khofmann\ApiError\ApiError;
use Khofmann\Database\Database;
use PDO;

class Post implements JsonSerializable
{
  private int $id;
  // User is set if the post was fetched by an authenticated user
  private ?User $user;
  // Name and image are set if the post was fetched by a non authenticated user
  private ?string $name;
  private ?string $image;
  private string $content;
  private DateTime $postedAt;

  private function __construct(int $id, ?User $user, ?string $name, ?string $image, string $content, string $postedAt)
  {
    $this->id = $id;
    $this->user = $user;
    $this->name = $name;
    $this->image = $image;
    $this->content = $content;
    $this->postedAt = new DateTime($postedAt);
  }

  /*
  * Statics
  */

  public static function getByID(int $id): Post
  {
    $db = Database::getInstance();

    $stmt = $db->prepare(
      "SELECT
        *
      FROM
        egb_gaestebuch
      WHERE
        id = :ID"
    );
    $stmt->bindValue(":ID", $id);
    $stmt->execute();
    $data = $stmt->fetch();

    if (!$data) throw new Exception("NotFound");

    $user = User::getByID($data["benutzer_id"]);
    return new Post($data["id"], $user, null, null, $data["beitrag"], $data["zeitstempel"]);
  }

  public static function create(User $user, string $content, int $limit): array
  {
    $content = substr(trim($content), 0, 250);

    $db = Database::getInstance();

    $stmt = $db->prepare(
      "INSERT INTO
        egb_gaestebuch(benutzer_id, beitrag)
      VALUES(:USR, :CON)"
    );
    $stmt->bindValue(":USR", $user->getID());
    $stmt->bindValue(":CON", nl2br(htmlspecialchars($content)));

    $stmt->execute();
    $lastId = $db->lastInsertId();

    $stmt = $db->prepare(
      "SELECT
        COUNT(*)
      FROM
        egb_gaestebuch"
    );
    $stmt->execute();
    $count = $stmt->fetch(PDO::FETCH_COLUMN, 0);

    return [
      "pages" => intdiv($count, $limit + 1) + 1,
      "post" => Post::getByID($lastId)
    ];
  }

  public static function list(int $page, int $limit, bool $authed = false): array
  {
    $db = Database::getInstance();
    $stmt = $db->prepare(
      "SELECT
        COUNT(*)
      FROM
        egb_gaestebuch"
    );
    $stmt->execute();
    $count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
    $stmt = $db->prepare(
      "SELECT
        *
      FROM
        egb_gaestebuch
      LIMIT $limit
      OFFSET " . ($page * $limit)
    );
    $stmt->execute();
    $data = $stmt->fetchAll();

    $list = array_map(
      function ($item) use ($authed) {
        $user = User::getByID($item["benutzer_id"]);
        return new Post($item["id"], $authed ? $user : null, !$authed ? $user->getUsername() : null, !$authed ? $user->getImage() : null, $item["beitrag"], $item["zeitstempel"]);
      },
      $data
    );

    return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $list];
  }

  /*
  * Members
  */

  public function update(?string $content): Post
  {
    $db = Database::getInstance();

    $db->beginTransaction();

    $failed = [];
    $reason = [];
    if (!empty($content)) {
      $content = substr(trim($content), 0, 250);

      $stmt = $db->prepare("UPDATE egb_gaestebuch SET beitrag = :CON WHERE id = :ID");
      $stmt->bindValue(":CON", nl2br(htmlspecialchars($content)));
      $stmt->bindValue(":ID", $this->id);
      try {
        if (!$stmt->execute()) {
          $failed = ["content"];
          $reason = ["generic"];
        }
      } catch (Exception $e) {
        $failed = ["content"];
        if ($e->getCode() === "23000") {
          $pdoErr = $stmt->errorInfo()[1];
          $reason = ["SQL: $pdoErr"];
        } else $reason = ["{$e->getCode()}"];
      }
    }
    if (count($failed) > 0) {
      $db->rollBack();

      throw ApiError::failedUpdate($failed, $reason);
    }

    $db->commit();

    return Post::getByID($this->id);
  }

  public function delete(int $limit): array
  {
    $db = Database::getInstance();
    $stmt = $db->prepare("DELETE FROM egb_gaestebuch WHERE id = :ID");
    $stmt->bindValue(":ID", $this->id);
    $stmt->execute();

    $stmt = $db->prepare(
      "SELECT
        COUNT(*)
      FROM
        egb_gaestebuch"
    );
    $stmt->execute();
    $count = $stmt->fetch(PDO::FETCH_COLUMN, 0);

    return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $this];
  }

  /*
  * Getters
  */

  public function getId(): int
  {
    return $this->id;
  }

  public function getUser(): User
  {
    return $this->user;
  }

  public function getContent(): string
  {
    return $this->content;
  }

  public function getPostedAt(): DateTime
  {
    return $this->postedAt;
  }

  /*
  * JSON
  */

  public function jsonSerialize(): array
  {
    $user = $this->user ? $this->user : [
      "username" => $this->name,
      "image" => $this->image,
    ];

    return [
      'id' => $this->id,
      'user' => $user,
      'content' => $this->content,
      'postedAt' => $this->postedAt,
    ];
  }
}
