<?php

namespace App\Services;

use App\Models\RegulationDocument;
use App\Models\ZoneRule;
use App\Models\Zone;
use Illuminate\Support\Facades\Storage;

/**
 * AI-powered regulation document extraction service.
 *
 * Architecture:
 *   1. Extract text/chunks from document (DocumentTextExtractorService)
 *   2. If scanned PDF → GPT-4o Vision OCR on each "page" (stored as image or raw PDF bytes)
 *   3. Send chunks through GPT-4o with structured JSON schema to extract zone rules
 *   4. Merge & deduplicate extracted rules across chunks
 *   5. Store extracted_rules in the DB for admin review
 *   6. Admin approves → applyRules() pushes data into zone_rules table
 *
 * Extracted Rule Schema (per rule):
 * {
 *   "zone_name": "Residential Zone R1",
 *   "zone_code": "R1",
 *   "far_min": 1.0,
 *   "far_max": 1.5,
 *   "ground_coverage_pct": 40,
 *   "max_floors": 4,
 *   "max_height_m": 15,
 *   "road_width_required_m": 9,
 *   "front_setback_m": 3,
 *   "rear_setback_m": 3,
 *   "side_setback_m": 1.5,
 *   "permitted_uses": ["residential", "home office"],
 *   "prohibited_uses": ["industrial", "slaughterhouse"],
 *   "special_conditions": "Basement allowed for parking only",
 *   "source_text": "...excerpt from document...",
 *   "confidence": 0.92
 * }
 */
class RegulationExtractorService
{
    private const SYSTEM_PROMPT = <<<'PROMPT'
You are an expert in Indian urban planning, development control regulations (DCR), and zoning laws.
Your task is to extract structured zone rules from regulation document text.

IMPORTANT RULES:
- Extract ONLY what is explicitly stated. Do NOT invent values.
- Return a valid JSON object with a "rules" array.
- For numbers, use numeric types (not strings). Use null for unstated values.
- confidence is a 0.0-1.0 float indicating your certainty.
- permitted_uses and prohibited_uses must be lowercase English arrays.
- zone_code should be the short code like "R1", "R2", "C1", "I1", etc.
- All measurements in metric (metres). Convert if document uses feet: 1 ft = 0.3048 m.
- If a range is given (e.g. FAR 1.0 to 2.0), use far_min and far_max.

Return JSON format:
{
  "rules": [
    {
      "zone_name": "...",
      "zone_code": "...",
      "far_min": null,
      "far_max": null,
      "ground_coverage_pct": null,
      "max_floors": null,
      "max_height_m": null,
      "road_width_required_m": null,
      "front_setback_m": null,
      "rear_setback_m": null,
      "side_setback_m": null,
      "permitted_uses": [],
      "prohibited_uses": [],
      "special_conditions": null,
      "source_text": "...",
      "confidence": 0.8
    }
  ],
  "document_type": "DCR|Building Bylaw|Master Plan|Other",
  "jurisdiction": "...",
  "year": null
}
PROMPT;

    public function __construct(
        private readonly DocumentTextExtractorService $extractor,
        private readonly OpenAIService                $ai,
    ) {}

    // ── Main entry point ──────────────────────────────────────────────────────

    /**
     * Process a RegulationDocument: extract text, run AI, store results.
     * Called from ProcessRegulationDocument job.
     */
    public function process(RegulationDocument $doc): void
    {
        if (! \App\Services\OpenAIService::isEnabled()) {
            $doc->update([
                'extraction_status' => 'failed',
                'extraction_error'  => 'AI features are disabled. Please configure your OpenAI API key in Admin → Settings → AI Settings.',
            ]);
            return;
        }

        $doc->update(['extraction_status' => 'processing']);

        try {
            $filePath = Storage::disk('local')->path($doc->storage_path);
            $extracted = $this->extractor->extract($filePath, $doc->document_type);

            if ($extracted['error']) {
                throw new \RuntimeException($extracted['error']);
            }

            $allRules    = [];
            $aiMeta      = ['chunks_processed' => 0, 'total_tokens' => 0, 'model' => ''];
            $textChunks  = [];

            if ($extracted['needs_vision']) {
                // Scanned PDF — send raw PDF bytes to GPT-4o Vision
                $result = $this->extractViaVision($filePath, $doc->document_type);
                $allRules   = $result['rules'];
                $aiMeta     = array_merge($aiMeta, $result['meta']);
                $textChunks = ['[Scanned document — text extracted via AI Vision OCR]'];
            } else {
                // Digital PDF or Word — process chunks
                foreach ($extracted['chunks'] as $idx => $chunk) {
                    $result = $this->extractChunk($chunk, $idx + 1, count($extracted['chunks']));
                    $allRules  = array_merge($allRules, $result['rules'] ?? []);
                    $aiMeta['total_tokens'] += $result['meta']['total_tokens'] ?? 0;
                    $aiMeta['model']         = $result['meta']['model'] ?? $aiMeta['model'];
                    $aiMeta['chunks_processed']++;
                }
                $textChunks = $extracted['chunks'];
            }

            // Deduplicate rules by zone_code
            $deduped = $this->deduplicateRules($allRules);

            $doc->update([
                'extraction_status'    => 'completed',
                'extracted_text_chunks'=> $textChunks,
                'extracted_rules'      => $deduped,
                'ai_metadata'          => $aiMeta,
                'extraction_error'     => null,
            ]);

        } catch (\Throwable $e) {
            $doc->update([
                'extraction_status' => 'failed',
                'extraction_error'  => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    // ── Chunk-based extraction ────────────────────────────────────────────────

    private function extractChunk(string $chunk, int $chunkNo, int $total): array
    {
        $userContent = "Document chunk {$chunkNo} of {$total}:\n\n{$chunk}";

        $result = $this->ai->chatWithMeta(self::SYSTEM_PROMPT, $userContent);

        return [
            'rules' => $result['content']['rules'] ?? [],
            'meta'  => [
                'total_tokens' => $result['total_tokens'],
                'model'        => $result['model'],
            ],
        ];
    }

    // ── Vision-based extraction (scanned PDFs) ────────────────────────────────

    private function extractViaVision(string $filePath, string $fileType): array
    {
        // For scanned PDFs: encode the entire PDF as base64 and send to Vision
        // GPT-4o can read PDF pages as images when base64-encoded
        $fileBytes = file_get_contents($filePath);
        if ($fileBytes === false) {
            throw new \RuntimeException("Could not read file: {$filePath}");
        }

        $base64 = base64_encode($fileBytes);
        $mime   = $fileType === 'pdf' ? 'application/pdf' : 'image/png';

        $prompt = self::SYSTEM_PROMPT . "\n\nThis is a scanned document. "
            . "First perform OCR to read all text, then extract zone rules as specified. "
            . "Return the same JSON format with a 'rules' array.";

        $result = $this->ai->visionAnalyzeJson($base64, $mime, $prompt);

        return [
            'rules' => $result['rules'] ?? [],
            'meta'  => ['total_tokens' => 0, 'model' => 'gpt-4o-vision'],
        ];
    }

    // ── Deduplication ─────────────────────────────────────────────────────────

    /**
     * Merge rules extracted from multiple chunks, preferring higher-confidence versions.
     */
    private function deduplicateRules(array $rules): array
    {
        $indexed = [];
        foreach ($rules as $rule) {
            $key = strtolower(trim($rule['zone_code'] ?? $rule['zone_name'] ?? uniqid()));
            if (! isset($indexed[$key]) || ($rule['confidence'] ?? 0) > ($indexed[$key]['confidence'] ?? 0)) {
                $indexed[$key] = $rule;
            }
        }
        return array_values($indexed);
    }

    // ── Apply rules to zone_rules table ──────────────────────────────────────

    /**
     * Push AI-extracted and admin-approved rules into the zone_rules table.
     * Called after admin approves a document.
     *
     * @param  RegulationDocument $doc
     * @param  array              $selectedRuleIndexes  Which of the extracted_rules to apply (empty = all)
     */
    public function applyRules(RegulationDocument $doc, array $selectedRuleIndexes = []): int
    {
        $rules     = $doc->extracted_rules ?? [];
        $stateId   = $doc->state_id;
        $cityId    = $doc->city_id;
        $applied   = 0;

        if ($selectedRuleIndexes) {
            $rules = array_intersect_key($rules, array_flip($selectedRuleIndexes));
        }

        foreach ($rules as $rule) {
            // Find matching zone by code
            $zoneQuery = Zone::where('state_id', $stateId);
            if ($cityId) {
                $zoneQuery->where('city_id', $cityId);
            }
            $zone = $zoneQuery->where('code', $rule['zone_code'] ?? '')->first()
                ?? $zoneQuery->whereRaw('LOWER(name) = ?', [strtolower($rule['zone_name'] ?? '')])->first();

            if (! $zone) {
                // Auto-create zone if it doesn't exist
                $zone = Zone::create([
                    'state_id'    => $stateId,
                    'city_id'     => $cityId,
                    'name'        => $rule['zone_name'] ?? 'Unknown Zone',
                    'code'        => strtoupper($rule['zone_code'] ?? 'Z' . rand(1, 99)),
                    'description' => 'Auto-created from regulation document: ' . $doc->title,
                    'is_active'   => true,
                ]);
            }

            // Upsert zone rule (create or update by zone_id + road_width bucket)
            ZoneRule::updateOrCreate(
                ['zone_id' => $zone->id, 'road_width_min_m' => $rule['road_width_required_m'] ?? 0],
                [
                    'far'                    => $rule['far_max'] ?? $rule['far_min'] ?? null,
                    'ground_coverage_pct'    => $rule['ground_coverage_pct'] ?? null,
                    'max_floors'             => $rule['max_floors'] ?? null,
                    'max_height_m'           => $rule['max_height_m'] ?? null,
                    'front_setback_m'        => $rule['front_setback_m'] ?? null,
                    'rear_setback_m'         => $rule['rear_setback_m'] ?? null,
                    'side_setback_m'         => $rule['side_setback_m'] ?? null,
                    'permitted_uses'         => json_encode($rule['permitted_uses'] ?? []),
                    'notes'                  => $rule['special_conditions'] ?? null,
                    'source_document_id'     => $doc->id,
                ]
            );
            $applied++;
        }

        $doc->update(['rules_applied' => true]);
        return $applied;
    }
}
