<?php
namespace boru\ai\Conversation;

use boru\ai\Config\ConfigResolver;
use boru\ai\Schema\Message;
use boru\ai\Schema\Request;
use boru\ai\Schema\Response;
use boru\ai\Schema\ToolCall;
use boru\ai\Schema\ToolResult;

class ConversationReplayService
{
    public function buildReplayMessages(Conversation $conversation, Request $currentRequest = null, array $options = array())
    {
        $strategy = $this->resolveStrategy($options);
        return $strategy->buildReplayMessages($conversation, $currentRequest);
    }

    public function applyReplay(Request $request, Conversation $conversation, array $options = array())
    {
        $workingRequest = $request->copy();
        $replayed = $this->buildReplayMessages($conversation, $request, $options);
        if (empty($replayed)) {
            return $workingRequest;
        }

        $workingRequest->messages(array_merge($replayed, $workingRequest->messages()));
        return $workingRequest;
    }

    public function messagesFromTurn(Turn $turn, array $options = array())
    {
        $messages = array();
        $request = $turn->request();
        if ($request instanceof Request) {
            foreach ($request->messages() as $message) {
                if ($message instanceof Message) {
                    $messages[] = $message;
                }
            }
        }

        $response = $turn->response();
        if ($response instanceof Response && is_string($response->text()) && trim($response->text()) !== '') {
            $messages[] = new Message('assistant', array($response->text()));
        }

        if ($this->optionBool($options, 'tool_data.enabled', false)) {
            $toolMessage = $this->buildToolDataMessage($turn, $options);
            if ($toolMessage instanceof Message) {
                $messages[] = $toolMessage;
            }
        }

        return $messages;
    }

    public function resolvedOptions($providerName, ConfigResolver $resolver, array $providerDefaults = array())
    {
        $defaults = $this->normalizeReplayOptions(array_merge(array(
            'enabled' => false,
            'strategy' => 'full',
            'max_turns' => 10,
            'summary' => array(
                'keep_turns' => 3,
                'max_chars' => 4000,
            ),
            'tool_data' => array(
                'enabled' => false,
                'max_chars' => 2000,
                'args_max_chars' => 600,
                'output_max_chars' => 1000,
            ),
        ), $providerDefaults));

        $resolved = array(
            'enabled' => $resolver->resolveProviderSetting($providerName, 'replay.enabled', $defaults['enabled']),
            'strategy' => $resolver->resolveProviderSetting($providerName, 'replay.strategy', $defaults['strategy']),
            'max_turns' => $resolver->resolveProviderSetting($providerName, 'replay.max_turns', $defaults['max_turns']),
            'summary' => array(
                'keep_turns' => $resolver->resolveProviderSetting($providerName, 'replay.summary.keep_turns', $defaults['summary']['keep_turns']),
                'max_chars' => $resolver->resolveProviderSetting($providerName, 'replay.summary.max_chars', $defaults['summary']['max_chars']),
            ),
            'tool_data' => array(
                'enabled' => $resolver->resolveProviderSetting($providerName, 'replay.tool_data.enabled', $defaults['tool_data']['enabled']),
                'max_chars' => $resolver->resolveProviderSetting($providerName, 'replay.tool_data.max_chars', $defaults['tool_data']['max_chars']),
                'args_max_chars' => $resolver->resolveProviderSetting($providerName, 'replay.tool_data.args_max_chars', $defaults['tool_data']['args_max_chars']),
                'output_max_chars' => $resolver->resolveProviderSetting($providerName, 'replay.tool_data.output_max_chars', $defaults['tool_data']['output_max_chars']),
            ),
        );

        return $this->normalizeReplayOptions($resolved);
    }

    public function summarizeTurns(array $turns, $maxChars = 4000)
    {
        $maxChars = (int)$maxChars > 0 ? (int)$maxChars : 4000;
        if (empty($turns)) {
            return '';
        }

        $lines = array();
        $lines[] = 'Conversation summary of earlier context:';
        $lines[] = 'Summarized prior turns are provided for continuity. Prefer the most recent replayed turns if any detail conflicts.';
        $lines[] = '';

        foreach ($turns as $turn) {
            if (!$turn instanceof Turn) {
                continue;
            }

            $turnNumber = $turn->number();
            $label = $turnNumber !== null ? 'Turn ' . $turnNumber : 'Turn';
            $requestText = $this->extractRequestText($turn);
            $responseText = $this->extractResponseText($turn);

            if ($requestText === '' && $responseText === '') {
                continue;
            }

            $chunk = $label . ':';
            if ($requestText !== '') {
                $chunk .= "\n- User: " . $requestText;
            }
            if ($responseText !== '') {
                $chunk .= "\n- Assistant: " . $responseText;
            }

            $candidate = implode("\n", $lines);
            if ($candidate !== '') {
                $candidate .= "\n\n";
            }
            $candidate .= $chunk;

            if (strlen($candidate) > $maxChars) {
                $remaining = $maxChars - strlen(implode("\n", $lines));
                if ($remaining > 32) {
                    $chunk = $this->truncateText($chunk, $remaining - 2);
                    $lines[] = '';
                    $lines[] = $chunk;
                }
                break;
            }

            $lines[] = '';
            $lines[] = $chunk;
        }

        return $this->truncateText(trim(implode("\n", $lines)), $maxChars);
    }

    protected function buildToolDataMessage(Turn $turn, array $options = array())
    {
        $lines = array();
        $toolCalls = $turn->toolCalls();
        $toolResults = $turn->toolResults();
        $argsMaxChars = $this->optionInt($options, 'tool_data.args_max_chars', 600);
        $outputMaxChars = $this->optionInt($options, 'tool_data.output_max_chars', 1000);

        if (empty($toolCalls) && empty($toolResults)) {
            return null;
        }

        $turnNumber = $turn->number();
        $label = $turnNumber !== null ? 'Turn ' . $turnNumber : 'Turn';
        $lines[] = $label . ' tool activity:';

        foreach ($toolCalls as $toolCall) {
            if (!$toolCall instanceof ToolCall) {
                continue;
            }

            $toolCallLine = '- Tool call';
            if ($toolCall->name()) {
                $toolCallLine .= ' ' . $toolCall->name();
            }
            if ($toolCall->id()) {
                $toolCallLine .= ' [id=' . $toolCall->id() . ']';
            }
            if ($toolCall->status()) {
                $toolCallLine .= ' status=' . $toolCall->status();
            }

            $argumentsJson = $toolCall->argumentsJson();
            if (!is_string($argumentsJson) || trim($argumentsJson) === '') {
                $arguments = $toolCall->arguments();
                if (!empty($arguments)) {
                    $argumentsJson = json_encode($arguments);
                }
            }
            if (is_string($argumentsJson) && trim($argumentsJson) !== '') {
                $toolCallLine .= ' args=' . $this->truncateText($this->normalizeSummaryText($argumentsJson), $argsMaxChars);
            }

            $lines[] = $toolCallLine;
        }

        foreach ($toolResults as $toolResult) {
            if (!$toolResult instanceof ToolResult) {
                continue;
            }

            $toolResultLine = '- Tool result';
            if ($toolResult->name()) {
                $toolResultLine .= ' ' . $toolResult->name();
            }
            if ($toolResult->toolCallId()) {
                $toolResultLine .= ' [tool_call_id=' . $toolResult->toolCallId() . ']';
            }
            $toolResultLine .= ' success=' . ($toolResult->success() ? 'true' : 'false');

            $outputText = $this->stringifyToolOutput($toolResult->output());
            if ($outputText !== '') {
                $toolResultLine .= ' output=' . $this->truncateText($outputText, $outputMaxChars);
            }

            $lines[] = $toolResultLine;
        }

        $maxChars = $this->optionInt($options, 'tool_data.max_chars', 2000);
        $text = implode("\n", $lines);
        $text = $this->truncateText($text, $maxChars);

        return new Message('assistant', array($text), array(
            'replay_kind' => 'tool_activity',
        ));
    }

    protected function normalizeReplayOptions(array $options)
    {
        $summary = isset($options['summary']) && is_array($options['summary']) ? $options['summary'] : array();
        $toolData = isset($options['tool_data']) && is_array($options['tool_data']) ? $options['tool_data'] : array();

        return array(
            'enabled' => $this->toBool(isset($options['enabled']) ? $options['enabled'] : false),
            'strategy' => isset($options['strategy']) ? (string)$options['strategy'] : 'full',
            'max_turns' => $this->sanitizePositiveInt(isset($options['max_turns']) ? $options['max_turns'] : 10, 10),
            'summary' => array(
                'keep_turns' => $this->sanitizeNonNegativeInt(isset($summary['keep_turns']) ? $summary['keep_turns'] : 3, 3),
                'max_chars' => $this->sanitizePositiveInt(isset($summary['max_chars']) ? $summary['max_chars'] : 4000, 4000),
            ),
            'tool_data' => array(
                'enabled' => $this->toBool(isset($toolData['enabled']) ? $toolData['enabled'] : false),
                'max_chars' => $this->sanitizePositiveInt(isset($toolData['max_chars']) ? $toolData['max_chars'] : 2000, 2000),
                'args_max_chars' => $this->sanitizePositiveInt(isset($toolData['args_max_chars']) ? $toolData['args_max_chars'] : 600, 600),
                'output_max_chars' => $this->sanitizePositiveInt(isset($toolData['output_max_chars']) ? $toolData['output_max_chars'] : 1000, 1000),
            ),
        );
    }

    protected function optionBool(array $options, $path, $default)
    {
        return $this->toBool($this->optionValue($options, $path, $default));
    }

    protected function optionInt(array $options, $path, $default)
    {
        return $this->sanitizePositiveInt($this->optionValue($options, $path, $default), $default);
    }

    protected function optionValue(array $options, $path, $default)
    {
        $segments = explode('.', (string)$path);
        $value = $options;

        foreach ($segments as $segment) {
            if (!is_array($value) || !array_key_exists($segment, $value)) {
                return $default;
            }
            $value = $value[$segment];
        }

        return $value;
    }

    protected function sanitizePositiveInt($value, $default)
    {
        $int = (int)$value;
        return $int > 0 ? $int : (int)$default;
    }

    protected function sanitizeNonNegativeInt($value, $default)
    {
        $int = (int)$value;
        return $int >= 0 ? $int : (int)$default;
    }

    protected function toBool($value)
    {
        if (is_bool($value)) {
            return $value;
        }
        if (is_string($value)) {
            $normalized = strtolower(trim($value));
            if ($normalized === '1' || $normalized === 'true' || $normalized === 'yes' || $normalized === 'on') {
                return true;
            }
            if ($normalized === '0' || $normalized === 'false' || $normalized === 'no' || $normalized === 'off' || $normalized === '') {
                return false;
            }
        }
        return $value ? true : false;
    }

    protected function stringifyToolOutput($output)
    {
        if (is_string($output)) {
            return $this->normalizeSummaryText($output);
        }

        if (is_scalar($output) || $output === null) {
            return $this->normalizeSummaryText((string)$output);
        }

        $json = json_encode($output);
        if (!is_string($json) || $json === '') {
            return '';
        }

        return $this->normalizeSummaryText($json);
    }

    protected function extractRequestText(Turn $turn)
    {
        $request = $turn->request();
        if (!$request instanceof Request) {
            return '';
        }

        return $this->messagesToText($request->messages());
    }

    protected function extractResponseText(Turn $turn)
    {
        $response = $turn->response();
        if (!$response instanceof Response || !is_string($response->text())) {
            return '';
        }

        return $this->normalizeSummaryText($response->text());
    }

    protected function messagesToText(array $messages)
    {
        $chunks = array();
        foreach ($messages as $message) {
            if (!$message instanceof Message) {
                continue;
            }

            $parts = array();
            foreach ($message->parts() as $part) {
                if (!is_object($part) || !method_exists($part, 'type') || !method_exists($part, 'value')) {
                    continue;
                }
                if ($part->type() !== 'text') {
                    continue;
                }
                $value = $part->value();
                if (!is_string($value) || trim($value) === '') {
                    continue;
                }
                $parts[] = trim($value);
            }

            if (!empty($parts)) {
                $chunks[] = implode(' ', $parts);
            }
        }

        return $this->normalizeSummaryText(implode(' | ', $chunks));
    }

    protected function normalizeSummaryText($text)
    {
        $text = trim((string)$text);
        if ($text === '') {
            return '';
        }

        $text = preg_replace('/\s+/', ' ', $text);
        return trim($text);
    }

    protected function truncateText($text, $maxChars)
    {
        $text = (string)$text;
        $maxChars = (int)$maxChars;
        if ($maxChars <= 0 || strlen($text) <= $maxChars) {
            return $text;
        }
        if ($maxChars <= 3) {
            return substr($text, 0, $maxChars);
        }

        return rtrim(substr($text, 0, $maxChars - 3)) . '...';
    }

    protected function resolveStrategy(array $options = array())
    {
        $normalized = $this->normalizeReplayOptions($options);
        $strategy = strtolower((string)$normalized['strategy']);
        if ($strategy === 'last_n_turns') {
            return new LastNTurnsReplayStrategy($normalized['max_turns'], $this, $normalized);
        }
        if ($strategy === 'summarize_history') {
            return new SummarizedHistoryReplayStrategy($normalized['summary']['keep_turns'], $normalized['summary']['max_chars'], $this, $normalized);
        }

        return new FullReplayStrategy($this, $normalized);
    }
}
