<?php
namespace CAE\Modules\ContentEngine;

use CAE\Lib\Database;
use CAE\Modules\QAEngine\QAEngine;
use RuntimeException;

class ContentGenerator
{
    private const MAX_RETRIES = 4;

    private Database         $db;
    private VariableInjector $injector;
    private OpenAIClient     $openai;
    private QAEngine         $qa;

    public function __construct()
    {
        $this->db       = Database::getInstance();
        $this->injector = new VariableInjector();
        $this->openai   = new OpenAIClient();
        $this->qa       = new QAEngine();
    }

    /**
     * Generate content for a single topic.
     */
    public function generate(int $topicId): array
    {
        // 1. Fetch topic
        $topic = $this->db->fetch(
            "SELECT t.*, pt.name AS page_type_name
             FROM topics t
             LEFT JOIN page_types pt ON pt.id = t.page_type_id
             WHERE t.id = ?",
            [$topicId]
        );
        if (!$topic) {
            throw new RuntimeException("Topic #{$topicId} not found");
        }

        // 2. Fetch project
        $project = $this->db->fetch("SELECT * FROM projects WHERE id = ?", [$topic['project_id']]);
        if (!$project) {
            throw new RuntimeException("Project not found for topic #{$topicId}");
        }

        // 3. Fetch page type
        $pageType = $this->db->fetch("SELECT * FROM page_types WHERE id = ?", [$topic['page_type_id']]);
        if (!$pageType) {
            throw new RuntimeException("Page type not found for topic #{$topicId}");
        }
        foreach (['structure_rules', 'seo_rules', 'content_blocks'] as $f) {
            if (!empty($pageType[$f]) && is_string($pageType[$f])) {
                $pageType[$f] = json_decode($pageType[$f], true) ?? [];
            }
        }

        // 4. Fetch active prompt template
        $template = $this->db->fetch(
            "SELECT * FROM prompt_templates
             WHERE page_type_id = ? AND is_active = 1
             ORDER BY version DESC LIMIT 1",
            [$topic['page_type_id']]
        );
        if (!$template) {
            throw new RuntimeException("No active prompt template for page type \"{$pageType['name']}\"");
        }

        // 5. Build variables, inject prompts, append QA requirements
        $variables    = $this->injector->buildVariables($project, $topic, $pageType);
        $systemPrompt = $this->injector->inject($template['system_prompt'], $variables);
        $userPrompt   = $this->injector->inject($template['user_prompt'],   $variables);
        $userPrompt   = $this->injector->appendQARequirements($userPrompt, $variables);

        // 6. Mark topic as generating
        $this->db->update('topics', ['status' => 'generating', 'updated_at' => date('Y-m-d H:i:s')], 'id = :id', ['id' => $topicId]);

        // Allow up to 5 minutes for retries
        set_time_limit(300);
        $startTime         = microtime(true);
        $totalInputTokens  = 0;
        $totalOutputTokens = 0;

        // 7. Initial generation
        $response           = $this->openai->chat($systemPrompt, $userPrompt);
        $totalInputTokens  += $response['input_tokens'];
        $totalOutputTokens += $response['output_tokens'];
        $duration           = (int)((microtime(true) - $startTime) * 1000);
        $currentContent     = $response['content'];

        // 8. Store initial result (will be updated on each successful retry)
        $insertData = [
            'content_code'       => $this->generateCode(),
            'topic_id'           => $topicId,
            'prompt_used'        => json_encode(['system' => $systemPrompt, 'user' => $userPrompt]),
            'output'             => $currentContent,
            'input_tokens'       => $totalInputTokens,
            'output_tokens'      => $totalOutputTokens,
            'model_used'         => $response['model'],
            'generation_time_ms' => $duration,
            'status'             => 'draft',
            'created_at'         => date('Y-m-d H:i:s'),
            'updated_at'         => date('Y-m-d H:i:s'),
        ];
        try {
            $contentId = $this->db->insert('generated_content', $insertData);
        } catch (\PDOException $e) {
            if (str_contains($e->getMessage(), 'content_code')) {
                unset($insertData['content_code']);
                $contentId = $this->db->insert('generated_content', $insertData);
            } else {
                throw $e;
            }
        }

        // 9. QA check + retry loop (up to MAX_RETRIES attempts)
        $qaResult   = $this->qa->check($currentContent, $project, $topic);
        $retryCount = 0;

        while (!$qaResult['passed'] && $retryCount < self::MAX_RETRIES) {
            $retryCount++;

            $repairSystem   = $this->buildRepairPrompt($systemPrompt, $qaResult, $topic, $retryCount);
            $retryResponse  = $this->openai->chat($repairSystem, $userPrompt);
            $totalInputTokens  += $retryResponse['input_tokens'];
            $totalOutputTokens += $retryResponse['output_tokens'];
            $currentContent = $retryResponse['content'];
            $qaResult       = $this->qa->check($currentContent, $project, $topic);
        }

        // 10. Persist final content version + token totals + status
        $finalStatus = $qaResult['passed'] ? 'draft' : 'qa_failed';
        $this->db->update('generated_content', [
            'output'             => $currentContent,
            'input_tokens'       => $totalInputTokens,
            'output_tokens'      => $totalOutputTokens,
            'generation_time_ms' => (int)((microtime(true) - $startTime) * 1000),
            'status'             => $finalStatus,
            'updated_at'         => date('Y-m-d H:i:s'),
        ], 'id = :id', ['id' => $contentId]);

        // 11. Log QA result
        $this->db->insert('qa_logs', [
            'content_id'          => $contentId,
            'issues_found'        => json_encode($qaResult['issues']),
            'checks_detail'       => json_encode($qaResult['checks'] ?? []),
            'passed'              => $qaResult['passed'] ? 1 : 0,
            'word_count'          => $qaResult['word_count'],
            'keyword_density'     => $qaResult['keyword_density'],
            'readability_score'   => $qaResult['readability_score']   ?? 0,
            'avg_sentence_length' => $qaResult['avg_sentence_length'] ?? 0,
            'paragraph_count'     => $qaResult['paragraph_count']     ?? 0,
            'unique_word_ratio'   => $qaResult['unique_word_ratio']   ?? 0,
            'h1_count'            => $qaResult['h1_count'],
            'retry_count'         => $retryCount,
            'created_at'          => date('Y-m-d H:i:s'),
        ]);

        // 12. Update topic status
        if ($qaResult['passed']) {
            $this->db->update('topics', ['status' => 'generated', 'updated_at' => date('Y-m-d H:i:s')], 'id = :id', ['id' => $topicId]);
        } else {
            $this->db->update('topics', ['status' => 'failed', 'updated_at' => date('Y-m-d H:i:s')], 'id = :id', ['id' => $topicId]);
        }

        $content = $this->db->fetch("SELECT * FROM generated_content WHERE id = ?", [$contentId]);
        $qaLog   = $this->db->fetch("SELECT * FROM qa_logs WHERE content_id = ? ORDER BY id DESC LIMIT 1", [$contentId]);

        return [
            'success'    => $qaResult['passed'],
            'content_id' => $contentId,
            'content'    => $content,
            'qa_log'     => $qaLog,
            'topic'      => $topic,
            'message'    => $qaResult['passed'] ? 'Content generated successfully' : 'Content failed QA after retry',
        ];
    }

    private function buildRepairPrompt(string $baseSystem, array $qaResult, array $topic, int $attempt): string
    {
        $fixes = [];
        foreach ($qaResult['checks'] as $check) {
            if ($check['passed'] || !empty($check['info'])) continue;
            switch ($check['name']) {
                case 'Word Count':
                    $target  = (int)($topic['target_word_count'] ?? 1200);
                    $min     = (int)round($target * 0.7);
                    $secCount = max(3, (int)round($target / 250));
                    $secWords = (int)round($target / $secCount);
                    $fixes[] = "WORD COUNT CRITICAL: You wrote only {$check['actual']} — the article MUST be at least {$min} words (target: {$target}). "
                        . "Rewrite the FULL article with ~{$secCount} main sections of ~{$secWords} words each. "
                        . "For every H2 section: write a full opening sentence, 2–3 detailed paragraphs with real examples or steps, and a closing sentence. "
                        . "Do NOT summarise. Do NOT skip any section. Do NOT stop before reaching {$target} words.";
                    break;
                case 'Keyword Present':
                case 'Keyword Density':
                    $kw      = $topic['primary_keyword'] ?? '';
                    $fixes[] = "KEYWORD FIX: The exact phrase \"{$kw}\" must appear at least 3 times. Include it in: (1) the opening paragraph, (2) one H2 heading or the sentence immediately following an H2, (3) the closing section.";
                    break;
                case 'H1 Heading':
                    $fixes[] = "H1 FIX: {$check['actual']}. Use exactly one <h1> tag for the article title — no more, no less.";
                    break;
                case 'H2 Structure':
                    $fixes[] = "H2 FIX: {$check['actual']}. Add at least 3 clearly distinct <h2> section headings to organise the content.";
                    break;
                case 'No "Conclusion" Heading':
                    $fixes[] = "HEADING FIX: You used \"Conclusion\" as a section heading — this is NOT allowed. Replace it with a specific, action-oriented heading that reflects what the closing section is actually about (e.g. \"Your Next Step\", \"Start Today\", \"What This Means for You\").";
                    break;
                case 'Banned Words':
                    $fixes[] = "COMPLIANCE FIX: Remove these banned words/phrases immediately: {$check['actual']}. Replace every instance with compliant alternatives.";
                    break;
                case 'Readability (Flesch)':
                    $fixes[] = "READABILITY FIX — 7th–10th Grade Required: Score {$check['actual']} — need ≥ 60. "
                        . "Rewrite every sentence with these rules: "
                        . "(1) Keep sentences under 18 words — mix short punchy ones (6–10 words) with medium (14–18 words). "
                        . "(2) Use ONLY everyday words a 14-year-old would know — replace jargon, technical terms, and corporate language. "
                        . "(3) Write in active voice: 'Teachers use this method' NOT 'This method is used by teachers'. "
                        . "(4) Each paragraph: maximum 3 sentences. "
                        . "(5) Remove: 'it is important to note', 'in conclusion', 'furthermore', 'moreover', 'nevertheless', 'subsequently'. "
                        . "(6) Write like a knowledgeable teacher explaining to curious secondary school students.";
                    break;
                case 'AI Content Score':
                    $fixes[] = "AI CONTENT SCORE FIX: Score {$check['actual']} — need ≥ 55/100. Your content reads too much like AI text. Fix: (1) VARY sentence lengths dramatically — mix short punchy sentences (5–8 words) with medium (15–25 words) and longer ones (25–35 words). (2) START sentences with diverse words — avoid repeating the same opener. (3) REMOVE these AI clichés entirely: \"it is important to note\", \"it's worth noting\", \"in conclusion\", \"dive into\", \"delve into\", \"game-changer\", \"in today's world\", \"in the realm of\". (4) ADD specific numbers, real examples, and concrete details. (5) WRITE like a human expert speaking to a colleague.";
                    break;
            }
        }

        $fixList = implode("\n\n", $fixes);
        return $baseSystem
            . "\n\n=== QA REPAIR REQUIRED — Attempt {$attempt} of " . self::MAX_RETRIES . " ==="
            . "\nYour previous response FAILED the following checks. Fix EVERY issue before responding:"
            . "\n\n" . $fixList
            . "\n\nRewrite the COMPLETE article from start to finish addressing every fix above."
            . " Output full HTML — do not summarise, do not stop early.";
    }

    private function generateCode(): string
    {
        $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
        $suffix = '';
        for ($i = 0; $i < 6; $i++) {
            $suffix .= $chars[random_int(0, strlen($chars) - 1)];
        }
        return 'CAE-' . date('Ym') . '-' . $suffix;
    }

    /**
     * Generate content for multiple topics.
     */
    public function generateBatch(array $topicIds): array
    {
        $results = [];
        foreach ($topicIds as $topicId) {
            try {
                $results[$topicId] = $this->generate((int)$topicId);
            } catch (\Exception $e) {
                // Mark topic as failed
                try {
                    $this->db->update('topics', ['status' => 'failed', 'updated_at' => date('Y-m-d H:i:s')], 'id = :id', ['id' => (int)$topicId]);
                } catch (\Exception $ignored) {}

                $results[$topicId] = [
                    'success'    => false,
                    'content_id' => null,
                    'message'    => $e->getMessage(),
                ];
            }
        }
        return $results;
    }
}
