<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreDocumentTemplateRequest;
use App\Http\Requests\UpdateDocumentTemplateRequest;
use App\Models\Action;
use App\Models\DocumentTemplate;
use App\Services\Documents\TokenScanner;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Inertia\Inertia;

class DocumentTemplateController extends Controller
{
    public function index()
    {
        $this->ensurePermission();
        $templates = DocumentTemplate::query()->orderByDesc('updated_at')->get();
        $actions = Action::with(['decisions:id,name'])->orderBy('name')->get(['id', 'name']);
        $actionsMapped = $actions->map(fn ($a) => [
            'id' => $a->id,
            'name' => $a->name,
            'decisions' => $a->decisions->map(fn ($d) => ['id' => $d->id, 'name' => $d->name])->values(),
        ]);

        return Inertia::render('Admin/DocumentTemplates/Index', [
            'templates' => $templates,
            'actions' => $actionsMapped,
        ]);
    }

    public function toggleActive(DocumentTemplate $template)
    {
        $this->ensurePermission();
        $template->active = ! $template->active;
        $template->updated_by = Auth::id();
        $template->save();

        return redirect()->back()->with('success', 'Status predloge posodobljen.');
    }

    public function show(DocumentTemplate $template)
    {
        $this->ensurePermission();

        return Inertia::render('Admin/DocumentTemplates/Show', [
            'template' => $template,
        ]);
    }

    public function edit(DocumentTemplate $template)
    {
        $this->ensurePermission();
        $actions = Action::with(['decisions:id,name'])->orderBy('name')->get(['id', 'name']);
        $actionsMapped = $actions->map(fn ($a) => [
            'id' => $a->id,
            'name' => $a->name,
            'decisions' => $a->decisions->map(fn ($d) => ['id' => $d->id, 'name' => $d->name])->values(),
        ]);

        return Inertia::render('Admin/DocumentTemplates/Edit', [
            'template' => $template,
            'actions' => $actionsMapped,
        ]);
    }

    public function updateSettings(UpdateDocumentTemplateRequest $request, DocumentTemplate $template)
    {
        $this->ensurePermission();
        $template->fill($request->only([
            'output_filename_pattern', 'date_format', 'action_id', 'decision_id', 'activity_note_template',
        ]));
        // If both action & decision provided, ensure decision belongs to action (parity with import templates)
        if ($request->filled('action_id') && $request->filled('decision_id')) {
            $belongs = \DB::table('action_decision')
                ->where('action_id', $request->integer('action_id'))
                ->where('decision_id', $request->integer('decision_id'))
                ->exists();
            if (! $belongs) {
                return redirect()->back()->withErrors(['decision_id' => 'Izbrana odločitev ne pripada izbrani akciji.']);
            }
        } elseif ($request->filled('action_id') && ! $request->filled('decision_id')) {
            // Allow clearing decision when action changes
            if ($template->isDirty('action_id')) {
                $template->decision_id = null;
            }
        }
        if ($request->has('fail_on_unresolved')) {
            $template->fail_on_unresolved = (bool) $request->boolean('fail_on_unresolved');
        }
        // Build formatting options array from discrete fields if provided
        $fmt = $template->formatting_options ?? [];
        $dirty = false;
        foreach ([
            'number_decimals', 'decimal_separator', 'thousands_separator',
            'currency_symbol', 'currency_position',
        ] as $key) {
            if ($request->filled($key)) {
                $fmt[$key] = $request->input($key);
                $dirty = true;
            } elseif ($request->has($key) && $request->input($key) === null) {
                unset($fmt[$key]);
                $dirty = true;
            }
        }
        if ($request->has('currency_space')) {
            $fmt['currency_space'] = (bool) $request->boolean('currency_space');
            $dirty = true;
        }
        if ($request->filled('default_date_format')) {
            $fmt['default_date_format'] = $request->input('default_date_format');
            $dirty = true;
        }
        if ($request->has('date_formats')) {
            $fmt['date_formats'] = array_filter((array) $request->input('date_formats'), fn ($v) => $v !== null && $v !== '');
            $dirty = true;
        }
        if ($dirty) {
            $template->formatting_options = $fmt;
        }
        // Merge meta, including custom_defaults
        if ($request->has('meta') && is_array($request->input('meta'))) {
            $meta = array_filter($request->input('meta'), fn ($v) => $v !== null && $v !== '');
            $template->meta = array_replace($template->meta ?? [], $meta);
        }
        $template->updated_by = Auth::id();
        $template->save();

        return redirect()->back()->with('success', 'Nastavitve predloge shranjene.');
    }

    public function rescanTokens(DocumentTemplate $template)
    {
        $this->ensurePermission();

        // Best-effort: read stored DOCX from disk and re-scan tokens
        $tokens = [];
        try {
            /** @var TokenScanner $scanner */
            $scanner = app(TokenScanner::class);
            $zip = new \ZipArchive;
            $tmp = tempnam(sys_get_temp_dir(), 'tmpl');
            // Copy file from storage to a temp path
            $disk = 'public';
            $stream = \Storage::disk($disk)->get($template->file_path);
            file_put_contents($tmp, $stream);
            if ($zip->open($tmp) === true) {
                // Collect main document and header/footer parts
                $parts = [];
                for ($i = 0; $i < $zip->numFiles; $i++) {
                    $stat = $zip->statIndex($i);
                    $name = $stat['name'] ?? '';
                    if (preg_match('#^word\/(document|header\d+|footer\d+)\.xml$#i', $name)) {
                        $parts[] = $name;
                    }
                }
                if (empty($parts)) {
                    $parts = ['word/document.xml'];
                }
                $found = [];
                foreach ($parts as $name) {
                    $xml = $zip->getFromName($name);
                    if ($xml === false) {
                        continue;
                    }
                    $norm = self::normalizeDocxXmlTokens($xml);
                    $det = $scanner->scan($norm);
                    if (! empty($det)) {
                        $found = array_merge($found, $det);
                    }
                }
                $tokens = array_values(array_unique($found));
                $zip->close();
            }
        } catch (\Throwable $e) {
            // swallow scanning errors, keep $tokens as empty
        }

        if (\Schema::hasColumn('document_templates', 'tokens')) {
            $template->tokens = $tokens;
        }
        // Auto-detect custom.* tokens and ensure meta.custom_default_types has defaults
        try {
            $meta = is_array($template->meta) ? $template->meta : [];
            $types = isset($meta['custom_default_types']) && is_array($meta['custom_default_types']) ? $meta['custom_default_types'] : [];
            $defaults = isset($meta['custom_defaults']) && is_array($meta['custom_defaults']) ? $meta['custom_defaults'] : [];
            foreach (($tokens ?? []) as $tok) {
                if (is_string($tok) && str_starts_with($tok, 'custom.')) {
                    $key = substr($tok, 7);
                    if ($key !== '') {
                        if (! array_key_exists($key, $types)) {
                            $types[$key] = 'string';
                        }
                        if (! array_key_exists($key, $defaults)) {
                            $defaults[$key] = '';
                        }
                    }
                }
            }
            if (! empty($types)) {
                $meta['custom_default_types'] = $types;
            }
            if (! empty($defaults)) {
                $meta['custom_defaults'] = $defaults;
            }
            if ($meta !== ($template->meta ?? [])) {
                $template->meta = $meta;
            }
        } catch (\Throwable $e) {
            // ignore meta typing/defaults failures
        }
        $template->updated_by = Auth::id();
        $template->save();

        $count = is_array($tokens) ? count($tokens) : 0;

        return back()->with('success', "Tokens posodobljeni ({$count} najdenih).");
    }

    public function store(StoreDocumentTemplateRequest $request)
    {
        $this->ensurePermission();
        $file = $request->file('file');

        // Basic extension guard (defense in depth vs only MIME detection)
        if (strtolower($file->getClientOriginalExtension()) !== 'docx') {
            return redirect()->back()->withErrors(['file' => 'Datoteka mora biti DOCX.']);
        }

        $slug = Str::slug($request->slug);

        // Determine next version if slug exists
        $latest = DocumentTemplate::where('slug', $slug)->orderByDesc('version')->first();
        $nextVersion = $latest ? ($latest->version + 1) : 1;

        $hash = hash_file('sha256', $file->getRealPath());
        $path = $file->store("document-templates/{$slug}/v{$nextVersion}", 'public');

        // Scan tokens from uploaded DOCX (best effort) – normalize XML to collapse Word run boundaries
        $tokens = [];
        try {
            /** @var TokenScanner $scanner */
            $scanner = app(TokenScanner::class);
            $zip = new \ZipArchive;
            $tmp = tempnam(sys_get_temp_dir(), 'tmpl');
            copy($file->getRealPath(), $tmp);
            if ($zip->open($tmp) === true) {
                // Collect main document and header/footer parts
                $parts = [];
                for ($i = 0; $i < $zip->numFiles; $i++) {
                    $stat = $zip->statIndex($i);
                    $name = $stat['name'] ?? '';
                    if (preg_match('#^word\/(document|header\d+|footer\d+)\.xml$#i', $name)) {
                        $parts[] = $name;
                    }
                }
                if (empty($parts)) {
                    $parts = ['word/document.xml'];
                }
                $found = [];
                foreach ($parts as $name) {
                    $xml = $zip->getFromName($name);
                    if ($xml === false) {
                        continue;
                    }
                    $norm = self::normalizeDocxXmlTokens($xml);
                    $det = $scanner->scan($norm);
                    if (! empty($det)) {
                        $found = array_merge($found, $det);
                    }
                }
                $tokens = array_values(array_unique($found));
                $zip->close();
            }
        } catch (\Throwable $e) {
            // swallow scanning errors
        }

        // (Future) Could refine allowed columns automatically based on tokens
        $entities = ['contract', 'client_case', 'client', 'person', 'account'];
        $columns = [
            'contract' => ['reference', 'start_date', 'end_date', 'description'],
            'client_case' => ['client_ref'],
            'client' => [],
            'person' => ['full_name', 'first_name', 'last_name', 'nu'],
            // Add common account attributes; whitelist may further refine
            'account' => ['reference', 'initial_amount', 'balance_amount', 'promise_date'],
        ];

        $payload = [
            'name' => $request->name,
            'slug' => $slug,
            'custom_name' => $request->custom_name,
            'description' => $request->description,
            'core_entity' => 'contract',
            'entities' => $entities,
            'columns' => $columns,
            'version' => $nextVersion,
            'engine' => 'tokens',
            'file_path' => $path,
            'file_hash' => $hash,
            'file_size' => $file->getSize(),
            'mime_type' => $file->getMimeType(),
            'active' => true,
            'created_by' => $latest ? $latest->created_by : Auth::id(), // preserve original author for lineage if re-upload
            'updated_by' => Auth::id(),
            'formatting_options' => [
                'number_decimals' => 2,
                'decimal_separator' => ',',
                'thousands_separator' => '.',
                'currency_symbol' => '€',
                'currency_position' => 'after',
                'currency_space' => true,
            ],
        ];
        // Optional meta + activity linkage fields (parity with import templates style)
        if ($request->filled('meta') && is_array($request->input('meta'))) {
            $payload['meta'] = array_filter($request->input('meta'), fn ($v) => $v !== null && $v !== '');
        }
        if ($request->filled('action_id')) {
            $payload['action_id'] = $request->integer('action_id');
        }
        if ($request->filled('decision_id')) {
            $payload['decision_id'] = $request->integer('decision_id');
        }
        if ($request->filled('activity_note_template')) {
            $payload['activity_note_template'] = $request->input('activity_note_template');
        }
        if (Schema::hasColumn('document_templates', 'tokens')) {
            $payload['tokens'] = $tokens;
        }
        // Auto-add default string types for any detected custom.* tokens
        try {
            $meta = isset($payload['meta']) && is_array($payload['meta']) ? $payload['meta'] : [];
            $types = isset($meta['custom_default_types']) && is_array($meta['custom_default_types']) ? $meta['custom_default_types'] : [];
            $defaults = isset($meta['custom_defaults']) && is_array($meta['custom_defaults']) ? $meta['custom_defaults'] : [];
            foreach (($tokens ?? []) as $tok) {
                if (is_string($tok) && str_starts_with($tok, 'custom.')) {
                    $key = substr($tok, 7);
                    if ($key !== '') {
                        if (! array_key_exists($key, $types)) {
                            $types[$key] = 'string';
                        }
                        if (! array_key_exists($key, $defaults)) {
                            $defaults[$key] = '';
                        }
                    }
                }
            }
            if (! empty($types)) {
                $meta['custom_default_types'] = $types;
            }
            if (! empty($defaults)) {
                $meta['custom_defaults'] = $defaults;
            }
            if ($meta !== ($payload['meta'] ?? [])) {
                $payload['meta'] = $meta;
            }
        } catch (\Throwable $e) {
            // ignore meta typing/defaults failures
        }
        $template = DocumentTemplate::create($payload);

        return redirect()->back()->with('success', 'Predloga uspešno shranjena. (v'.$template->version.')')->with('template_id', $template->id);
    }

    private function ensurePermission(): void
    {
        if (Gate::denies('manage-document-templates') && Gate::denies('manage-settings')) {
            abort(403);
        }
    }

    /**
     * Collapse common Word run boundaries and proofing spans so tokens like {{client.person.full_name}}
     * appear contiguous in XML for scanning.
     */
    private static function normalizeDocxXmlTokens(string $xml): string
    {
        // Remove proofing error markers
        $xml = preg_replace('#<w:proofErr[^>]*/>#i', '', $xml) ?? $xml;
        // Iteratively collapse boundaries between text runs, even if w:rPr is present
        $patterns = [
            '#</w:t>\s*</w:r>\s*(?:<w:proofErr[^>]*/>\s*)*(?:<w:r[^>]*>\s*(?:<w:rPr>.*?</w:rPr>\s*)*)?<w:t[^>]*>#is',
        ];
        $prev = null;
        while ($prev !== $xml) {
            $prev = $xml;
            foreach ($patterns as $pat) {
                $xml = preg_replace($pat, '', $xml) ?? $xml;
            }
        }
        // Remove zero-width and soft hyphen characters
        $xml = str_replace(["\xE2\x80\x8B", "\xC2\xAD"], '', $xml);

        return $xml;
    }
}
