<?php

declare(strict_types=1);

require_once APPPATH . 'libraries/Cf_REST_Controller.php';
require_once __DIR__ . '/../dto/AiResponseDto.php';

/**
 * AI Reporter API Controller
 */
class Ai_reporter_api extends Cf_REST_Controller
{
    private const HTTP_OK = 200;
    private const BAD_REQUEST = 400;
    private const QUERY_LIMIT = 10;
    private const RETRY_ERROR_MESSAGE_TEMPLATE = 'Previous SQL query failed. Please provide a corrected SQL query for: %s. Previous SQL: %s';

    private string $username;

    public function __construct()
    {
        parent::__construct();
        $this->username = (string) $this->session->userdata('username');
        $this->load->model('advancedreports_model');
        $this->load->model('aireporter_model');
    }

    /**
     * Handle POST requests for AI message processing
     *
     * Processes user messages, generates SQL queries via AI, executes them,
     * and returns formatted results or explanations.
     *
     * @return void
     */
    public function message_post(): void
    {
        $requestData = $this->parseRequestBody();
        $aiResponse = $this->getAiResponse($requestData['message'], $requestData['history']);

        if ($aiResponse->hasSql()) {
            $this->handleSqlQuery($aiResponse, $requestData);
        } else {
            $this->respond(self::HTTP_OK, $aiResponse->explanation);
        }
    }

    /**
     * @return array{message: string, history: array}
     */
    private function parseRequestBody(): array
    {
        $requestData = json_decode($this->request->body, true) ?? [];

        return [
            'message' => $requestData['message'] ?? '',
            'history' => $requestData['history'] ?? [],
        ];
    }

    /**
     * Get AI response for the given message and history
     *
     * @param string $message User message
     * @param array $history Conversation history
     * @return AiResponseDto
     */
    private function getAiResponse(string $message, array $history): AiResponseDto
    {
        try {
            $response = $this->aireporter_model->message($message, $history);
            return AiResponseDto::fromArray($response);
        } catch (Exception $e) {
            $this->respond(self::BAD_REQUEST, $e->getMessage());
            exit();
        }
    }

    /**
     * Handle SQL query execution with retry logic
     *
     * @param AiResponseDto $aiResponse Initial AI response
     * @param array $requestData
     * @return void
     */
    private function handleSqlQuery(AiResponseDto $aiResponse, array $requestData): void
    {
        $queryResult = $this->executeQuery($aiResponse, $requestData['message']);

        if ($queryResult === false) {
            $this->retryQueryExecution($aiResponse, $requestData);
        }
    }

    /**
     * Retry query execution with error feedback to AI
     *
     * @param AiResponseDto $previousAiResponse Previous AI response that failed
     * @param array $requestData request data
     * @return void
     */
    private function retryQueryExecution(AiResponseDto $previousAiResponse, array $requestData): void
    {
        $errorMessage = sprintf(self::RETRY_ERROR_MESSAGE_TEMPLATE, $requestData['message'], $previousAiResponse->sql);

        $retryAiResponse = $this->getAiResponse($errorMessage, $requestData['history']);

        if ($retryAiResponse->hasSql()) {
            $retryResult = $this->executeQuery($retryAiResponse, $requestData['message']);

            if ($retryResult === false) {
                $this->respond(self::BAD_REQUEST, 'SQL execution failed.');
            }
        } else {
            $this->respond(self::HTTP_OK, $retryAiResponse->explanation);
        }
    }

    /**
     * Execute SQL query and return formatted response
     *
     * @param AiResponseDto $aiResponse AI response containing SQL query
     * @param string $originalMessage Original user message for link generation
     * @return bool True on success, false on failure
     */
    private function executeQuery(AiResponseDto $aiResponse, string $originalMessage): bool
    {
        try {
            $queryData = $this->runSqlQuery($aiResponse->sql);
            $reportData = $this->formatReportData($queryData);
            $reportLink = $this->generateReportLink($aiResponse->sql, $originalMessage);

            $response = [
                'data' => $reportData,
                'reportLink' => $reportLink,
                ...$aiResponse->toArray(),
            ];

            $this->respond(self::HTTP_OK, json_encode($response));
            return true;
        } catch (Exception $exception) {
            return false;
        }
    }

    /**
     * Execute SQL query via advanced reports model
     *
     * @param string $sql SQL query to execute
     * @return array Query results
     * @throws Exception If query execution fails
     */
    private function runSqlQuery(string $sql): array
    {
        return $this->advancedreports_model->runQuery($this->username, $sql, limit: self::QUERY_LIMIT);
    }

    /**
     * Format query results into report data structure
     *
     * @param array $queryData Raw query results
     * @return array{headers: array, rows: array, total: int}
     */
    private function formatReportData(array $queryData): array
    {
        return [
            'headers' => array_map(fn(array $header): string => $header['columnName'], $queryData['header']),
            'rows' => $queryData['rows'],
            'total' => $queryData['rowCount'],
        ];
    }

    /**
     * Generate report link for the executed query
     *
     * @param string $sql Executed SQL query
     * @param string $title Report title
     * @return string Generated report link
     */
    private function generateReportLink(string $sql, string $title): string
    {
        $state = base64_encode(json_encode(['SQLSTRING' => $sql]));
        return '/reports/report/run?state=' . rawurlencode($state) . "&title={$title}";
    }
}
