<?php

declare(strict_types=1);

namespace App\Services\AiBiography;

use Illuminate\Support\Facades\Log;

/**
 * Coordinates prompt building, Vision gateway call, and result validation.
 *
 * v1.1 changes:
 *   – Accepts quality tier so the prompt builder can choose the right template.
 *   – One controlled retry on validation failure, using strict/conservative mode.
 *   – Returns prompt_version and was_retried in the result.
 *   – Logging improved to include retry reason and quality tier.
 *
 * Does NOT read or write to the database.
 * Does NOT know about user-edit flags or storage decisions.
 * Those responsibilities belong to AiBiographyService.
 */
class AiBiographyGenerator
{
    public function __construct(
        private readonly AiBiographyPromptBuilder $promptBuilder,
        private readonly VisionLlmClient $llmClient,
        private readonly AiBiographyValidator $validator,
    ) {
    }

    /**
     * Generate a biography from a normalized input payload.
     *
     * On validation failure a single controlled retry is performed with a
     * stricter/more conservative prompt. If the retry also fails, the result
     * reports the final combined errors alongside was_retried=true.
     *
     * Returns:
     *   success        bool
     *   text           string|null
     *   errors         list<string>
     *   model          string|null
     *   prompt_version string
     *   was_retried    bool
     *
     * @param  array<string, mixed>  $input        from AiBiographyInputBuilder::build()
     * @param  string                $qualityTier  'rich'|'medium'|'sparse'
     * @return array{success: bool, text: string|null, errors: list<string>, model: string|null, prompt_version: string, was_retried: bool}
     */
    public function generate(array $input, string $qualityTier = 'rich'): array
    {
        Log::info('AiBiographyGenerator: generation started', [
            'user_id'      => $input['user_id'] ?? null,
            'quality_tier' => $qualityTier,
        ]);

        $result = $this->attempt($input, $qualityTier, strict: false);

        if ($result['success']) {
            Log::info('AiBiographyGenerator: generation succeeded', [
                'user_id'       => $input['user_id'] ?? null,
                'prompt_version' => $result['prompt_version'],
                'quality_tier'   => $qualityTier,
            ]);

            return array_merge($result, ['was_retried' => false]);
        }

        // ── One retry with stricter prompt ───────────────────────────────────
        Log::info('AiBiographyGenerator: first attempt failed; retrying with strict prompt', [
            'user_id'        => $input['user_id'] ?? null,
            'quality_tier'   => $qualityTier,
            'first_errors'   => $result['errors'],
        ]);

        $retryResult = $this->attempt($input, $qualityTier, strict: true);

        if ($retryResult['success']) {
            Log::info('AiBiographyGenerator: retry succeeded', [
                'user_id'       => $input['user_id'] ?? null,
                'prompt_version' => $retryResult['prompt_version'],
            ]);

            return array_merge($retryResult, ['was_retried' => true]);
        }

        Log::warning('AiBiographyGenerator: retry also failed', [
            'user_id'      => $input['user_id'] ?? null,
            'first_errors' => $result['errors'],
            'retry_errors' => $retryResult['errors'],
        ]);

        return array_merge($retryResult, ['was_retried' => true]);
    }

    // -------------------------------------------------------------------------

    /**
     * Single generation attempt.
     *
     * @return array{success: bool, text: string|null, errors: list<string>, model: string|null, prompt_version: string}
     */
    private function attempt(array $input, string $qualityTier, bool $strict): array
    {
        $isSparse = $qualityTier === 'sparse';
        $payload  = $this->promptBuilder->build($input, strict: $strict, sparse: $isSparse);
        $promptVersion = (string) ($payload['prompt_version'] ?? AiBiographyPromptBuilder::PROMPT_VERSION);

        // Strip prompt_version before sending to the gateway (not a standard LLM field).
        $gatewayPayload = array_diff_key($payload, ['prompt_version' => true]);

        try {
            $rawText = $this->llmClient->chat($gatewayPayload);
        } catch (VisionLlmException $e) {
            Log::warning('AiBiographyGenerator: gateway failure', [
                'user_id' => $input['user_id'] ?? null,
                'error'   => $e->getMessage(),
                'code'    => $e->getCode(),
            ]);

            return [
                'success'        => false,
                'text'           => null,
                'errors'         => [$e->getMessage()],
                'model'          => null,
                'prompt_version' => $promptVersion,
            ];
        }

        $text = $this->normalizeOutput($rawText);

        $errors = $this->validator->validate($text, $qualityTier);

        if ($errors !== []) {
            Log::info('AiBiographyGenerator: validation rejected generated text', [
                'user_id'        => $input['user_id'] ?? null,
                'quality_tier'   => $qualityTier,
                'strict'         => $strict,
                'errors'         => $errors,
                'excerpt'        => mb_substr($text, 0, 120),
                'prompt_version' => $promptVersion,
            ]);

            return [
                'success'        => false,
                'text'           => null,
                'errors'         => $errors,
                'model'          => null,
                'prompt_version' => $promptVersion,
            ];
        }

        return [
            'success'        => true,
            'text'           => $text,
            'errors'         => [],
            'model'          => $this->llmClient->configuredModel(),
            'prompt_version' => $promptVersion,
        ];
    }

    /**
     * Strip model artifacts and normalize whitespace.
     */
    private function normalizeOutput(string $rawText): string
    {
        // Strip chain-of-thought reasoning blocks emitted by some models (e.g. <think>...</think>).
        $rawText = (string) preg_replace('/<think>.*?<\/think>/si', '', $rawText);

        // Strip common markdown formatting the model may add despite instructions.
        $rawText = (string) preg_replace('/\*\*([^*]+)\*\*/', '$1', $rawText); // **bold**
        $rawText = (string) preg_replace('/\*([^*\n]+)\*/', '$1', $rawText);   // *italic*
        $rawText = (string) preg_replace('/^#{1,6}\s+/m', '', $rawText);       // ## headings
        $rawText = (string) preg_replace('/`([^`]*)`/', '$1', $rawText);       // `code`

        // Normalize: collapse multiple consecutive newlines into a single space.
        $text = trim((string) preg_replace('/\n{2,}/', ' ', $rawText));
        $text = trim((string) preg_replace('/\s{2,}/', ' ', $text));

        return $text;
    }
}
