<?php

declare(strict_types=1);

namespace App\Context;

use App\Config\ContextServiceConfig;

/**
 * ContextService
 *
 * Manages conversational history persistence and retrieval.
 *
 * Responsibilities:
 * - Persist completed conversation turns (append-only)
 * - Provide recent or extended conversation context
 * - Resolve history storage paths safely
 *
 * Non-responsibilities:
 * - No follow-up detection
 * - No prompt semantics
 * - No interpretation of user intent
 *
 * Context levels:
 * - Regular context: last N lines (default)
 * - Full context: extended history for special cases
 */
final class ContextService
{
    private string $historyDir;

    public function __construct(
        string $historyDir,
        string $projectDir,
        private readonly ContextServiceConfig $config,
    )
    {
        /**
         * Normalize history directory:
         * - Allow relative paths in env (e.g. "var/agent-history")
         * - Always resolve to an absolute path based on project root
         */
        $historyDir = rtrim($historyDir, '/');

        if (!str_starts_with($historyDir, '/')) {
            $historyDir = rtrim($projectDir, '/') . '/' . ltrim($historyDir, '/');
        }

        $this->historyDir = $historyDir;

        // Ensure directory exists
        if (!is_dir($this->historyDir)) {
            mkdir($this->historyDir, 0777, true);
        }
    }

    /**
     * Returns the conversation context for a given user.
     *
     * @param string $userId Stable client identifier
     * @param bool $full Whether to load extended history
     */
    public function buildUserContext(string $userId, bool $full = true): string
    {
        $path = $this->getHistoryPath($userId);

        if (!is_file($path)) {
            return '';
        }

        $lines = file($path, FILE_IGNORE_NEW_LINES);
        if ($lines === false) {
            return '';
        }

        $maxLines = $full
            ? $this->config->getMaxFullLines()
            : $this->config->getMaxVisibleRegularLines();

        $selected = array_slice($lines, -$maxLines);

        return implode("\n", $selected);
    }

    /**
     * Returns as much recent history as possible within a character budget.
     *
     * Strategy:
     * - Preserve complete conversation turns whenever possible
     * - Prioritize the newest turns
     * - Only truncate when even the newest single turn is larger than the budget
     *
     * A turn starts with "Question: " and includes the corresponding assistant answer.
     */
    public function buildUserContextWithinBudget(string $userId, int $maxChars): string
    {
        if ($maxChars <= 0) {
            return '';
        }

        $history = $this->readHistoryFile($userId);
        if ($history === '') {
            return '';
        }

        $turns = $this->splitHistoryIntoTurns($history);
        if ($turns === []) {
            return $this->truncateTurnToBudget($history, $maxChars);
        }

        $selected = [];
        $currentLength = 0;

        for ($i = count($turns) - 1; $i >= 0; $i--) {
            $turn = $turns[$i];
            $turnLength = mb_strlen($turn);
            $separatorLength = $selected === [] ? 0 : 2; // "\n\n"

            if (($currentLength + $separatorLength + $turnLength) <= $maxChars) {
                array_unshift($selected, $turn);
                $currentLength += $separatorLength + $turnLength;
                continue;
            }

            // If nothing fits yet, keep at least the newest turn in truncated form.
            if ($selected === []) {
                $truncatedTurn = $this->truncateTurnToBudget($turn, $maxChars);
                if ($truncatedTurn !== '') {
                    $selected[] = $truncatedTurn;
                }
            }

            break;
        }

        return implode("\n\n", $selected);
    }

    /**
     * Appends a completed interaction to the user's history.
     *
     * Format (append-only):
     *   Question: <user prompt>
     *   <assistant response>
     */
    public function appendHistory(string $userId, string $prompt, string $response): void
    {
        $path = $this->getHistoryPath($userId);

        $entry = "Question: {$prompt}\n{$response}\n";
        file_put_contents($path, $entry, FILE_APPEND | LOCK_EX);
    }

    /**
     * Deletes the complete conversation history for a user.
     */
    public function deleteHistory(string $userId): void
    {
        $path = $this->getHistoryPath($userId);

        if (is_file($path)) {
            unlink($path);
        }
    }

    /**
     * Reads the raw history file contents for a user.
     */
    private function readHistoryFile(string $userId): string
    {
        $path = $this->getHistoryPath($userId);

        if (!is_file($path)) {
            return '';
        }

        $content = file_get_contents($path);

        if ($content === false) {
            return '';
        }

        return trim($content);
    }

    /**
     * Splits append-only history into complete turns.
     *
     * Each turn starts with "Question: ".
     */
    private function splitHistoryIntoTurns(string $history): array
    {
        $history = trim($history);
        if ($history === '') {
            return [];
        }

        $parts = preg_split('/(?=^Question:\s)/m', $history);
        if ($parts === false) {
            return [];
        }

        $turns = [];

        foreach ($parts as $part) {
            $part = trim($part);

            if ($part === '') {
                continue;
            }

            $turns[] = $part;
        }

        return $turns;
    }

    /**
     * Truncates a single turn to fit into the available budget.
     *
     * The start of the turn is preserved so the "Question: ..." marker remains intact.
     */
    private function truncateTurnToBudget(string $turn, int $maxChars): string
    {
        $turn = trim($turn);

        if ($turn === '' || $maxChars <= 0) {
            return '';
        }

        if (mb_strlen($turn) <= $maxChars) {
            return $turn;
        }

        if ($maxChars <= 3) {
            return mb_substr($turn, 0, $maxChars);
        }

        return rtrim(mb_substr($turn, 0, $maxChars - 3)) . '...';
    }

    /**
     * Resolves the absolute history file path for a user.
     */
    private function getHistoryPath(string $userId): string
    {
        $safeUserId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $userId);

        return $this->historyDir . '/' . $safeUserId . '.txt';
    }
}