<?php
/**
 * سودانا — TaskEstimator v1
 *
 * Converts a natural-language prompt into a priced, structured execution plan.
 * Pipeline: RuleParser → [AIAnalyzer if confidence < threshold] → Pricer
 *
 * RULES:
 * - All prices MUST come from PricingEngine (no hardcoded numbers)
 * - AI calls are logged to task_estimation_log, NOT charged to user
 * - Multipliers from estimation_multipliers table correct systematic drift
 */
require_once __DIR__ . '/PricingEngine.php';
require_once __DIR__ . '/Logger.php';

class TaskEstimator {

    // ── Constants ────────────────────────────────────────────
    private const DEFAULT_IMAGE_SIZE     = '1024x1024';
    private const DEFAULT_VIDEO_DURATION = '15s';
    private const DEFAULT_IMAGE_COUNT    = 3;
    private const PROMPT_MAX_CHARS       = 1000;

    /**
     * Main entry: analyze prompt → return EstimatedPlan or throw.
     *
     * @param string $prompt  Raw user prompt
     * @param int    $userId
     * @param string $plan    User subscription plan (free/plus/pro/corp)
     * @return array EstimatedPlan
     */
    public static function analyze(string $prompt, int $userId, string $plan = 'free'): array {
        // Enforce platform-configurable max length (fallback to class constant)
        $maxLen = (int)PricingEngine::getSetting('max_prompt_length', self::PROMPT_MAX_CHARS);
        $maxLen = max(50, min($maxLen, self::PROMPT_MAX_CHARS)); // bounds: 50-1000
        $prompt = mb_substr(trim($prompt), 0, $maxLen);
        if (!$prompt) throw new \InvalidArgumentException('empty_prompt');

        $startMs = (int)(microtime(true) * 1000);

        // ── 1. Check estimation limits ───────────────────────
        self::enforceEstimationLimits($userId);

        // ── 2. Rule-based parsing (always runs, free) ────────
        ['tasks' => $tasks, 'confidence' => $confidence] = self::ruleParser($prompt);

        $method = 'rules';
        $estimationCostUsd = 0.0;

        // ── 3. AI analyzer (only if confidence < threshold) ──
        $confidenceThreshold = (float)PricingEngine::getSetting('estimation_ai_confidence_min', 0.70);

        if ($confidence < $confidenceThreshold) {
            if (self::globalBudgetAvailable()) {
                ['tasks' => $aiTasks, 'confidence' => $aiConf, 'cost_usd' => $estimationCostUsd]
                    = self::aiAnalyzer($prompt, $tasks, $confidence);

                if ($aiConf > $confidence) {
                    $tasks      = $aiTasks;
                    $confidence = $aiConf;
                    $method     = $confidence >= $confidenceThreshold ? 'ai' : 'hybrid';
                }
            }
        }

        // ── 4. Apply adaptive multipliers ────────────────────
        $tasks = self::applyMultipliers($tasks);

        // ── 5. Price every task via PricingEngine ────────────
        $pricedTasks  = self::priceTasks($tasks, $plan);
        $priceSnapshot = self::buildPriceSnapshot($plan);

        // ── 6. Build final plan ──────────────────────────────
        $totalSdg = array_sum(array_column($pricedTasks, 'cost_sdg'));

        $plan_result = [
            'tasks'          => $pricedTasks,
            'cost'           => [
                'total_sdg'     => $totalSdg,
                'breakdown'     => self::buildBreakdown($pricedTasks),
            ],
            'price_snapshot' => $priceSnapshot,
            'confidence'     => round($confidence, 3),
            'method'         => $method,
        ];

        // ── 7. Log to task_estimation_log ────────────────────
        $elapsedMs = (int)(microtime(true) * 1000) - $startMs;
        $logId = self::logEstimation(
            $userId, $prompt, $plan, $method, $confidence,
            $elapsedMs, $estimationCostUsd, $plan_result, $totalSdg
        );

        $plan_result['estimation_log_id'] = $logId;

        Logger::info('task_estimated', [
            'user_id'    => $userId,
            'method'     => $method,
            'confidence' => $confidence,
            'total_sdg'  => $totalSdg,
            'tasks'      => count($pricedTasks),
            'ms'         => $elapsedMs,
        ]);

        return $plan_result;
    }

    // ── STAGE 1: Rule Parser ─────────────────────────────────────
    private static function ruleParser(string $prompt): array {
        $tasks      = [];
        $confidence = 0.0;
        $signals    = 0;
        $text       = mb_strtolower($prompt);

        // ── Document detection ───────────────────────────────
        $docTypes = [
            'feasibility'    => ['دراسة جدوى','جدوى اقتصادية','feasibility'],
            'business_plan'  => ['خطة أعمال','خطة عمل','business plan','خطة أعمال'],
            'contract'       => ['عقد','اتفاقية','contract','agreement'],
            'proposal'       => ['عرض تجاري','مقترح','proposal','offer'],
            'report'         => ['تقرير','report'],
            'memorandum'     => ['مذكرة','مذكرة قانونية','memo'],
            'company_profile'=> ['ملف شركة','بروفايل','profile'],
        ];

        $docSubtype   = null;
        $docConfBonus = 0.0;

        foreach ($docTypes as $subtype => $keywords) {
            foreach ($keywords as $kw) {
                if (str_contains($text, $kw)) {
                    $docSubtype = $subtype;
                    $docConfBonus = 0.20;
                    $signals++;
                    break 2;
                }
            }
        }

        // Generic document signals
        if (!$docSubtype && preg_match('/وثيقة|مستند|document|اكتب.{0,20}عن/u', $text)) {
            $docSubtype = 'report';
            $docConfBonus = 0.10;
            $signals++;
        }

        if ($docSubtype) {
            $tasks[] = ['type' => 'document', 'subtype' => $docSubtype, 'count' => 1];
        }

        // ── Image detection ──────────────────────────────────
        // Explicit count first
        $imageCount = 0;
        $imageSize  = self::DEFAULT_IMAGE_SIZE;

        if (preg_match('/(\d+)\s*صور|صورة/u', $text, $m)) {
            $imageCount = min((int)$m[1], 50);
            $signals++;
        } elseif (preg_match('/صور|images?/u', $text)) {
            $imageCount = self::DEFAULT_IMAGE_COUNT;
            $signals++;
            $docConfBonus -= 0.10; // implicit, less confident
        }

        // Size hints
        if (preg_match('/صغير|512/u', $text))        $imageSize = '512x512';
        elseif (preg_match('/كبير|طويل|1792/u', $text)) $imageSize = '1024x1792';

        // Only include images if provider is configured
        $_imgEnabled = (int)PricingEngine::getSetting('enable_image_generation', 0);
        if ($imageCount > 0 && $_imgEnabled) {
            $tasks[] = ['type' => 'image', 'subtype' => $imageSize,
                        'count' => $imageCount, 'size' => $imageSize];
        }

        // ── Diagram/Drawing detection ────────────────────────
        $diagramCount = 0;
        if (preg_match('/(\d+)\s*(رسوم|مخطط|diagram)/u', $text, $m)) {
            $diagramCount = min((int)$m[1], 20);
            $signals++;
        } elseif (preg_match('/رسم|مخطط|chart|diagram|infograph/u', $text)) {
            $diagramCount = 1;
            $signals++;
        }

        if ($diagramCount > 0) {
            $tasks[] = ['type' => 'diagram', 'subtype' => 'standard', 'count' => $diagramCount];
        }

        // ── Video detection ──────────────────────────────────
        $videoDuration = null;
        $durationMap   = [
            '60s' => ['60 ثانية','دقيقة','60s','one minute','1min'],
            '30s' => ['30 ثانية','30s'],
            '15s' => ['15 ثانية','15s','قصير'],
            '5s'  => ['5 ثانية','5s','قصير جداً'],
        ];

        if (preg_match('/فيديو|video|كليب|animation|موشن/u', $text)) {
            // Try to detect duration
            foreach ($durationMap as $dur => $hints) {
                foreach ($hints as $hint) {
                    if (str_contains($text, $hint)) {
                        $videoDuration = $dur;
                        break 2;
                    }
                }
            }
            // Default to cheapest if duration not specified
            $videoDuration = $videoDuration ?? self::DEFAULT_VIDEO_DURATION;
            // Only include video if provider is configured
            $_vidEnabled = (int)PricingEngine::getSetting('enable_video_generation', 0);
            if ($_vidEnabled) {
                $tasks[] = ['type' => 'video', 'subtype' => $videoDuration,
                            'count' => 1, 'duration' => $videoDuration];
            }
            $signals++;
        }

        // ── Table/Spreadsheet detection ──────────────────────
        if (preg_match('/(\d+)\s*جداول|جدول/u', $text, $m)) {
            $tableCount = min((int)($m[1] ?? 1), 20);
            $tasks[]    = ['type' => 'table', 'subtype' => 'standard', 'count' => $tableCount];
            $signals++;
        }

        // ── Complexity multiplier ────────────────────────────
        if (preg_match('/مفصّل|مفصل|شامل|كامل|detailed|comprehensive/u', $text)) {
            // Mark as complex — Pricer will apply multiplier
            foreach ($tasks as &$t) { $t['complex'] = true; }
            unset($t);
            $docConfBonus += 0.05;
        }

        // ── Sector detection (affects pricing tier) ──────────
        $sector = 'general';
        if (preg_match('/طبي|مستشفى|medical|health/u', $text))   $sector = 'medical';
        elseif (preg_match('/قانوني|محامي|legal|law/u', $text))   $sector = 'legal';
        elseif (preg_match('/تعليم|أكاديمي|education/u', $text))  $sector = 'education';

        foreach ($tasks as &$t) { $t['sector'] = $sector; }
        unset($t);

        // ── Confidence calculation ────────────────────────────
        // Base: 0.4 (we have something) + signals / 5 + bonuses
        if ($signals > 0) {
            $confidence = min(0.95, 0.40 + ($signals * 0.15) + $docConfBonus);
        }

        // If no tasks found at all, very low confidence
        if (empty($tasks)) {
            $tasks[] = ['type' => 'text', 'subtype' => 'general', 'count' => 1];
            $confidence = 0.30;
        }

        return ['tasks' => $tasks, 'confidence' => $confidence];
    }

    // ── STAGE 2: AI Analyzer ────────────────────────────────────
    private static function aiAnalyzer(string $prompt, array $existingTasks, float $existingConf): array {
        require_once __DIR__ . '/AIClient.php';
        require_once __DIR__ . '/InferenceEngine.php';

        // Try InferenceEngine first (free)
        $ie = InferenceEngine::infer($prompt);
        if (!empty($ie['sector']) && ($ie['confidence'] ?? 0) >= 0.75) {
            // InferenceEngine gave a confident answer — augment tasks with sector info
            foreach ($existingTasks as &$t) { $t['sector'] = $ie['sector']; }
            unset($t);
            return ['tasks' => $existingTasks, 'confidence' => $existingConf + 0.10, 'cost_usd' => 0.0];
        }

        // AI call — strict JSON-only prompt
        $systemPrompt = 'You are a task analyzer. Return ONLY valid JSON, no markdown, no explanation.
Schema:
{"tasks":[{"type":"document|image|video|diagram|table|text","subtype":"string","count":1,"size":"optional","duration":"optional"}],"confidence":0.0}
Rules:
- count must be an integer
- confidence 0.0 to 1.0
- If unclear, use defaults: image size=1024x1024, video duration=15s, count=1';

        $userPrompt = 'Analyze this Arabic project request and identify all tasks needed:
"' . mb_substr($prompt, 0, 300) . '"
Return JSON only.';

        $result   = AIClient::call($userPrompt, $systemPrompt, ['max_tokens' => 80]);
        $costUsd  = (float)($result['cost_usd'] ?? 0.0);
        $raw      = $result['text'] ?? '';

        // Parse JSON safely
        $raw = preg_replace('/```json|```/', '', $raw);
        $data = json_decode(trim($raw), true);

        if (!is_array($data) || empty($data['tasks'])) {
            // AI failed to parse → use existing
            return ['tasks' => $existingTasks, 'confidence' => $existingConf, 'cost_usd' => $costUsd];
        }

        // Validate and sanitize AI output
        $aiTasks = [];
        $validTypes = ['document', 'image', 'video', 'diagram', 'table', 'text'];

        foreach ($data['tasks'] as $t) {
            if (!in_array($t['type'] ?? '', $validTypes, true)) continue;
            $aiTasks[] = [
                'type'     => $t['type'],
                'subtype'  => preg_replace('/[^a-z0-9_×x]/', '', $t['subtype'] ?? 'general'),
                'count'    => min((int)($t['count'] ?? 1), 100),
                'size'     => $t['size'] ?? null,
                'duration' => $t['duration'] ?? null,
                'sector'   => 'general',
            ];
        }

        $aiConf = max(0.0, min(1.0, (float)($data['confidence'] ?? 0.5)));
        $tasks  = !empty($aiTasks) ? $aiTasks : $existingTasks;

        return ['tasks' => $tasks, 'confidence' => $aiConf, 'cost_usd' => $costUsd];
    }

    // ── STAGE 3: Adaptive Multipliers ───────────────────────────
    private static function applyMultipliers(array $tasks): array {
        static $multipliers = null;

        if ($multipliers === null) {
            try {
                $rows = db()->query(
                    "SELECT task_type, multiplier FROM estimation_multipliers"
                )->fetchAll(\PDO::FETCH_KEY_PAIR);
                $multipliers = $rows ?: [];
            } catch (\Throwable $e) {
                $multipliers = [];
            }
        }

        foreach ($tasks as &$t) {
            $key = $t['type'] . ':' . ($t['subtype'] ?? 'standard');
            if (isset($multipliers[$key]) && $multipliers[$key] != 1.0) {
                $t['count_multiplied'] = true;
                $t['multiplier']       = (float)$multipliers[$key];
            }
        }
        unset($t);

        return $tasks;
    }

    // ── STAGE 4: Pricer (PricingEngine ONLY) ────────────────────
    private static function priceTasks(array $tasks, string $userPlan): array {
        $priced = [];

        foreach ($tasks as $t) {
            $type    = $t['type'];
            $subtype = $t['subtype'] ?? 'standard';
            $count   = max(1, (int)($t['count'] ?? 1));
            $mult    = (float)($t['multiplier'] ?? 1.0);
            $complex = $t['complex'] ?? false;
            $complexMult = $complex ? 1.3 : 1.0;

            $unitCost = self::getUnitPrice($type, $subtype, $userPlan);

            // Apply multipliers
            $unitCostAdjusted = (int)round($unitCost * $mult * $complexMult);
            $totalCost        = $unitCostAdjusted * $count;

            $priced[] = array_merge($t, [
                'unit_cost_sdg' => $unitCostAdjusted,
                'cost_sdg'      => $totalCost,
                'plan_applied'  => $userPlan,
            ]);
        }

        return $priced;
    }

    private static function getUnitPrice(string $type, string $subtype, string $plan): int {
        switch ($type) {
            case 'document':
                return PricingEngine::getDocumentPrice($subtype, $plan);

            case 'image':
                $size    = $subtype ?: self::DEFAULT_IMAGE_SIZE;
                $col     = "price_{$plan}_sdg";
                if (!in_array($plan, ['free','plus','pro'], true)) $col = 'price_free_sdg';
                try {
                    $st = db()->prepare("SELECT $col FROM image_prices WHERE size_key=? AND is_active=1 LIMIT 1");
                    $st->execute([$size]);
                    return (int)($st->fetchColumn() ?: 0);
                } catch (\Throwable $e) { return 0; }

            case 'video':
                $dur  = $subtype ?: self::DEFAULT_VIDEO_DURATION;
                $col  = "price_{$plan}_sdg";
                if (!in_array($plan, ['free','plus','pro'], true)) $col = 'price_free_sdg';
                try {
                    $st = db()->prepare("SELECT $col FROM video_prices WHERE duration_key=? AND is_active=1 LIMIT 1");
                    $st->execute([$dur]);
                    return (int)($st->fetchColumn() ?: 0);
                } catch (\Throwable $e) { return 0; }

            case 'diagram':
                return (int)PricingEngine::getSetting("diagram_price_$plan", match($plan) {
                    'plus' => 400, 'pro' => 250, default => 600
                });

            case 'table':
                return (int)PricingEngine::getSetting("table_price_$plan", match($plan) {
                    'plus' => 300, 'pro' => 200, default => 500
                });

            case 'text':
                return 0; // within daily AI limit

            default:
                return 0;
        }
    }

    // ── Price Snapshot (lock prices at estimation time) ──────────
    private static function buildPriceSnapshot(string $plan): array {
        try {
            $data = PricingEngine::getAll();
        } catch (\Throwable $e) {
            $data = [];
        }

        return [
            'locked_at'    => date('c'),
            'ttl_seconds'  => (int)PricingEngine::getSetting('estimation_ttl_minutes', 30) * 60,
            'plan'         => $plan,
            'exchange_rate'=> PricingEngine::getRate(),
            'plans'        => $data['plans'] ?? [],
        ];
    }

    // ── Cost Breakdown ───────────────────────────────────────────
    private static function buildBreakdown(array $tasks): array {
        $breakdown = [];
        foreach ($tasks as $t) {
            $type = $t['type'];
            if (!isset($breakdown[$type])) {
                $breakdown[$type] = ['count' => 0, 'cost_sdg' => 0];
            }
            $breakdown[$type]['count']    += $t['count'] ?? 1;
            $breakdown[$type]['cost_sdg'] += $t['cost_sdg'];
        }
        return $breakdown;
    }

    // ── Estimation Limit Enforcement ─────────────────────────────
    private static function enforceEstimationLimits(int $userId): void {
        // Rate limit: 10/min per user
        $rateKey = "est_rl_$userId";
        if (function_exists('check_rate_limit') && !check_rate_limit($rateKey, 10, 60)) {
            throw new \RuntimeException('estimation_rate_limited');
        }

        // Daily cap per user
        $dailyCap = (int)PricingEngine::getSetting('estimation_daily_cap_user', 50);
        try {
            $st = db()->prepare(
                "SELECT COUNT(*) FROM task_estimation_log
                 WHERE user_id=? AND DATE(created_at)=CURDATE()"
            );
            $st->execute([$userId]);
            if ((int)$st->fetchColumn() >= $dailyCap) {
                throw new \RuntimeException('estimation_daily_cap_reached');
            }
        } catch (\RuntimeException $e) { throw $e; }
          catch (\Throwable $ignored) {}
    }

    // ── Global Platform Budget Check ─────────────────────────────
    private static function globalBudgetAvailable(): bool {
        $capUsd = (float)PricingEngine::getSetting('estimation_global_budget_usd_day', 1.00);
        try {
            $st = db()->query(
                "SELECT COALESCE(SUM(estimation_cost_usd),0) FROM task_estimation_log
                 WHERE DATE(created_at)=CURDATE() AND estimation_method IN ('ai','hybrid')"
            );
            return (float)$st->fetchColumn() < $capUsd;
        } catch (\Throwable $e) {
            return false; // Fail safe: no AI
        }
    }

    // ── Auto-suggestions when insufficient funds ─────────────────
    public static function buildSuggestions(array $plan, int $availableSdg): array {
        $suggestions = [];
        $tasks       = $plan['tasks'] ?? [];

        // Sort by cost descending (most expensive first)
        usort($tasks, fn($a,$b) => ($b['cost_sdg'] ?? 0) <=> ($a['cost_sdg'] ?? 0));

        foreach ($tasks as $t) {
            $type = $t['type'];
            $cost = (int)($t['cost_sdg'] ?? 0);
            $cnt  = (int)($t['count'] ?? 1);

            if ($type === 'video' && $cost > 0) {
                $suggestions[] = [
                    'action'    => 'reduce_video_duration',
                    'saving_sdg'=> $cost,
                    'label'     => "حذف الفيديو يوفّر " . number_format($cost) . " SDG",
                ];
            } elseif ($type === 'image' && $cnt > 1) {
                $half    = max(1, (int)ceil($cnt / 2));
                $savings = (int)round($cost / $cnt * ($cnt - $half));
                $suggestions[] = [
                    'action'    => 'reduce_images',
                    'from'      => $cnt,
                    'to'        => $half,
                    'saving_sdg'=> $savings,
                    'label'     => "تقليل الصور من $cnt إلى $half يوفّر " . number_format($savings) . " SDG",
                ];
            } elseif ($type === 'diagram' && $cnt > 0) {
                $suggestions[] = [
                    'action'    => 'remove_diagrams',
                    'saving_sdg'=> $cost,
                    'label'     => "حذف الرسومات يوفّر " . number_format($cost) . " SDG",
                ];
            }

            if (!empty($suggestions) && array_sum(array_column($suggestions, 'saving_sdg')) >= ($plan['cost']['total_sdg'] - $availableSdg)) {
                break; // Got enough suggestions
            }
        }

        // Add savings summary for UI
        $totalSavings = array_sum(array_column($suggestions, 'saving_sdg'));
        return ['items' => $suggestions, 'total_savings_possible' => $totalSavings];
    }

    // ── Logging ──────────────────────────────────────────────────
    private static function logEstimation(
        int $userId, string $prompt, string $plan,
        string $method, float $confidence, int $ms,
        float $costUsd, array $planData, int $totalSdg
    ): ?int {
        try {
            $st = db()->prepare(
                "INSERT INTO task_estimation_log
                 (user_id, trace_id, prompt_hash, prompt_length, user_plan,
                  estimation_method, confidence_score, estimation_ms,
                  estimation_cost_usd, estimated_plan, estimated_sdg, created_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())"
            );
            $st->execute([
                $userId,
                Logger::traceId(),
                md5($prompt),
                mb_strlen($prompt),
                $plan,
                $method,
                $confidence,
                $ms,
                $costUsd,
                json_encode($planData, JSON_UNESCAPED_UNICODE),
                $totalSdg,
            ]);
            return (int)db()->lastInsertId();
        } catch (\Throwable $e) {
            Logger::warn('estimation_log_failed', ['error' => $e->getMessage()]);
            return null;
        }
    }
}
