<?php
declare(strict_types=1);

namespace MyPrayerJournal;

use BitBadger\PgSQL\Documents\{ Configuration, Definition, Document, DocumentIndex, Query };
use MyPrayerJournal\Domain\{ AsOf, History, JournalRequest, Note, Request, RequestAction };

class Data
{
    /** The prayer request table */
    const REQ_TABLE = 'prayer_request';

    /**
     * Ensure the table and index exist
     */
    public static function startUp()
    {
        Configuration::$connectionString = "pgsql:host=localhost;port=5432;dbname=leafjson;user=leaf;password=leaf";
        Definition::ensureTable(self::REQ_TABLE);
        Definition::ensureIndex(self::REQ_TABLE, DocumentIndex::Optimized);
    }

    /**
     * Find a full prayer request by its ID
     * 
     * @param string $reqId The request ID
     * @param string $userId The ID of the currently logged-on user
     * @return ?Request The request, or null if it is not found
     */
    public static function findFullRequestById(string $reqId, string $userId): ?Request
    {
        $req = Document::findById(self::REQ_TABLE, $reqId, Request::class);
        return is_null($req) || $req->userId != $userId ? null : $req;
    }

    /**
     * Add a history entry to the specified request
     * 
     * @param string $reqId The request ID
     * @param string $userId The ID of the currently logged-on user
     * @param History $history The history entry to be added
     */
    public static function addHistory(string $reqId, string $userId, History $history)
    {
        $req = self::findFullRequestById($reqId, $userId);
        if (is_null($req)) throw new \InvalidArgumentException("$reqId not found");
        array_unshift($req->history, $history);
        Document::updateFull(self::REQ_TABLE, $reqId, $req);
    }

    /**
     * Add a note to the specified request
     * 
     * @param string $reqId The request ID
     * @param string $userId The ID of the currently logged-on user
     * @param Note $note The note to be added
     */
    public static function addNote(string $reqId, string $userId, Note $note)
    {
        $req = self::findFullRequestById($reqId, $userId);
        if (is_null($req)) throw new \InvalidArgumentException("$reqId not found");
        array_unshift($req->notes, $note);
        Document::updateFull(self::REQ_TABLE, $reqId, $req);
    }

    /**
     * Add a new request
     * 
     * @param Request $req The request to be added
     */
    public static function addRequest(Request $req)
    {
        Document::insert(self::REQ_TABLE, $req->id, $req);
    }

    /**
     * Map an array of `Request`s to an array of `JournalRequest`s
     * 
     * @param Request[] $reqs The requests to map
     * @param bool $full Whether to include history and notes (true) or not (false)
     * @return JournalRequest[] The journal request objects
     */
    private static function mapToJournalRequest(array $reqs, bool $full): array
    {
        return array_map(fn (Request $req) => new JournalRequest($req, $full), $reqs);
    }

    /**
     * Get journal requests for the given user by "answered" status
     * 
     * @param string $userId The ID of the user for whom requests should be retrieved
     * @param string $op The JSON Path operator to use for comparison (`==` or `<>`)
     * @return JournalRequest[] The journal request objects
     */
    private static function getJournalByAnswered(string $userId, string $op): array
    {
        $sql = Query::selectFromTable(self::REQ_TABLE)
            . ' WHERE ' . Query::whereDataContains(':criteria') . ' AND ' . Query::whereJsonPathMatches(':path');
        $params = [
            ':criteria' => Query::jsonbDocParam([ 'userId' => $userId ]),
            ':path'     => '$.history[*].action (@ ' . $op . ' "' . RequestAction::Answered->name . '")'
        ];
        return self::mapToJournalRequest(
            Document::customList($sql, $params, Request::class, Document::mapFromJson(...)), true);
    }
    
    /**
     * Retrieve all answered requests for this user
     * 
     * @param string $userId The ID of the user for whom answered requests should be retrieved
     * @return JournalRequest[] The answered requests
     */
    public static function getAnsweredRequests(string $userId): array
    {
        $answered = self::getJournalByAnswered($userId, '==');
        usort($answered, AsOf::newestToOldest(...));
        return $answered;
    }

    /**
     * Get the user's current prayer request journal
     * 
     * @param string $userId The ID of the user whose journal should be retrieved
     * @return JournalRequest[] The journal request objects
     */
    public static function getJournal(string $userId): array
    {
        $reqs = self::getJournalByAnswered($userId, '<>');
        usort($reqs, AsOf::oldestToNewest(...));
        return $reqs;
    }
}