<?php

namespace local_gamibot_manager\external;

use Exception;
use JsonException;

/**
 * Client for the Gamibot API.
 *
 * Compatible with Moodle 4.x and 5.
 */
class gami_api_client
{
    private string $baseUrl;
    private string $apiKey;
    private string $apiSecret;
    private int $timeout = 30;

    public function __construct(string $baseUrl, string $apiKey, string $apiSecret)
    {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->apiKey = $apiKey;
        $this->apiSecret = $apiSecret;
    }

    /**
     * Set request timeout in seconds.
     */
    public function set_timeout(int $seconds): void
    {
        $this->timeout = $seconds;
    }

    /**
     * Get User Stats for a specific Course.
     *
     * @param string $lmsUserId
     * @param string $lmsCourseId
     * @return array
     * @throws Exception
     */
    public function get_user_course_stats(string $lmsUserId, string $lmsCourseId): array
    {
        return $this->request('GET', '/enrollments/stats', [
            'lmsUserId' => $lmsUserId,
            'lmsCourseId' => $lmsCourseId
        ]);
    }

    /**
     * Create an Enrollment.
     *
     * @param string $lmsUserId
     * @param string $lmsCourseId
     * @param string|null $enrolledAt ISO 8601 Date string
     * @return array
     * @throws Exception
     */
    public function create_enrollment(string $lmsUserId, string $lmsCourseId, ?string $enrolledAt = null): array
    {
        $data = [
            'lmsUserId' => $lmsUserId,
            'lmsCourseId' => $lmsCourseId
        ];
        if ($enrolledAt) {
            $data['enrolledAt'] = $enrolledAt;
        }
        return $this->request('POST', '/enrollments', [], $data);
    }

    /**
     * Upsert a Topic.
     *
     * @param string $lmsId
     * @param string $title
     * @param string $lmsCourseId
     * @param string|null $description
     * @param bool $isActive
     * @return array
     * @throws Exception
     */
    public function upsert_topic(string $lmsId, string $lmsCourseId, string $title, ?string $description = null, bool $isActive = true): array
    {
        $data = [
            'lmsId' => $lmsId,
            'lmsCourseId' => $lmsCourseId,
            'title' => $title,
            'isActive' => $isActive
        ];
        if ($description)
            $data['description'] = $description;

        return $this->request('POST', '/topics', [], $data);
    }

    /**
     * Create a Course Badge (or Global if supported in future).
     *
     * @param string $title
     * @param string|null $lmsCourseId
     * @param string|null $description
     * @param string|null $icon
     * @return array
     * @throws Exception
     */
    public function create_badge(string $title, ?string $lmsCourseId = null, ?string $description = null, ?string $icon = null): array
    {
        $data = ['title' => $title];
        if ($lmsCourseId)
            $data['lmsCourseId'] = $lmsCourseId;
        if ($description)
            $data['description'] = $description;
        if ($icon)
            $data['icon'] = $icon;

        return $this->request('POST', '/badges', [], $data);
    }

    /**
     * Upsert a Conversation.
     *
     * @param string $lmsId
     * @param string $lmsUserId
     * @param string $lmsCourseId
     * @param string $mode 'clarify', 'quiz', 'summarize'
     * @param string|null $lmsTopicId
     * @param int|null $userMessageCount
     * @param int|null $quizScore
     * @param string|null $firstMessageAt ISO 8601 Date string
     * @param string|null $lastMessageAt ISO 8601 Date string
     * @return array
     * @throws Exception
     */
    public function upsert_conversation(
        string $lmsId,
        string $lmsUserId,
        string $lmsCourseId,
        string $mode,
        ?string $lmsTopicId = null,
        ?int $userMessageCount = null,
        ?int $quizScore = null,
        ?string $firstMessageAt = null,
        ?string $lastMessageAt = null
    ): array {
        $data = [
            'lmsId' => $lmsId,
            'lmsUserId' => $lmsUserId,
            'lmsCourseId' => $lmsCourseId,
            'mode' => $mode
        ];
        if ($lmsTopicId)
            $data['lmsTopicId'] = $lmsTopicId;
        if ($userMessageCount !== null)
            $data['userMessageCount'] = $userMessageCount;
        if ($quizScore !== null)
            $data['quizScore'] = $quizScore;
        if ($firstMessageAt)
            $data['firstMessageAt'] = $firstMessageAt;
        if ($lastMessageAt)
            $data['lastMessageAt'] = $lastMessageAt;

        return $this->request('POST', '/conversations', [], $data);
    }

    /**
     * Send HTTP Request.
     * @throws Exception
     */
    private function request(string $method, string $path, array $query = [], array $body = []): array
    {
        $url = $this->baseUrl . $path;
        if (!empty($query)) {
            $url .= '?' . http_build_query($query, null, '&', PHP_QUERY_RFC3986);
        }

        $ch = curl_init($url);

        $headers = [
            'Content-Type: application/json',
            'Accept: application/json',
            'x-moodle-api-key: ' . $this->apiKey,
            'x-moodle-api-secret: ' . $this->apiSecret
        ];

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        } else if ($method !== 'GET') {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
            if (!empty($body)) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
            }
        } else {
            curl_setopt($ch, CURLOPT_HTTPGET, true);
        }

        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new Exception("Gamibot API Connection Error: $error");
        }

        if ($statusCode >= 400) {
            $msg = "Gamibot API Error ($statusCode)";
            try {
                $json = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
                if (isset($json['message'])) {
                    $msg .= ': ' . $json['message'];
                }
            } catch (JsonException $e) {
                $msg .= ': ' . $response;
            }
            throw new Exception($msg, $statusCode);
        }

        try {
            return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException $e) {
            throw new Exception("Invalid JSON response from Gamibot API", 500);
        }
    }
}
