<?php

namespace App\Http\Controllers;

use App\Facades\Site;
use App\Http\Resources\ContentMigrationEntryResource;
use App\Imports\ContentMigrationImport;
use App\Jobs\CrawlSiteJob;
use App\Jobs\ImportSitemapJob;
use App\Services\ContentMigrationProcessor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;
use Maatwebsite\Excel\HeadingRowImport;
use Mtc\MercuryDataModels\ContentMigrationEntry;
use Mtc\MercuryDataModels\ContentMigrationTemplateMapping;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\Page;
use Mtc\MercuryDataModels\Property;
use Mtc\MercuryDataModels\Template;
use Mtc\MercuryDataModels\VehicleOffer;

class ContentMigrationController extends Controller
{
    public function __construct()
    {
        $this->middleware(['permission:migrate-content']);
    }

    public function index(Request $request): JsonResponse
    {
        $perPage = max(1, min((int) $request->input('per_page', 250), 1000));

        $paginator = ContentMigrationEntry::query()
            ->filtered($request->only('status', 'record_type', 'franchise_id', 'template_id', 'search', 'website_section', 'metas_ready'))
            ->with(['franchise', 'template'])
            ->orderBy('id')
            ->paginate($perPage);

        return ContentMigrationEntryResource::collection($paginator)
            ->additional(['stage_counts' => $this->stageCountsArray()])
            ->response();
    }

    public function stageCounts(): JsonResponse
    {
        return response()->json($this->stageCountsArray());
    }

    private function stageCountsArray(): array
    {
        // Pages and new_cars require a template; offers can be processed
        // from their static fields alone, so template_id isn't required there.
        $processingBase = ContentMigrationEntry::query()
            ->where('status', 'keep')
            ->where(function ($q) {
                $q->where(function ($sub) {
                    $sub->whereIn('record_type', ['page', 'new_car'])
                        ->whereNotNull('template_id');
                })->orWhere('record_type', 'offer');
            });

        return [
            'status' => ContentMigrationEntry::query()->count(),
            'structure' => ContentMigrationEntry::query()->where('status', 'keep')->count(),
            'processing' => (clone $processingBase)->count(),
            'processing_done' => (clone $processingBase)->whereNotNull('processed_at')->count(),
        ];
    }

    public function show(ContentMigrationEntry $entry): JsonResponse
    {
        $entry->load(['franchise', 'template']);

        return response()->json(new ContentMigrationEntryResource($entry));
    }

    public function upload(Request $request): JsonResponse
    {
        $request->validate([
            'file' => 'required|file|mimes:xlsx,xls,csv',
        ]);

        $file = $request->file('file');
        $fileKey = 'content-migration-' . Str::random(16);
        $path = $file->storeAs('temp', $fileKey . '.' . $file->getClientOriginalExtension(), 'local');

        // Read headers
        $headings = (new HeadingRowImport())->toArray($file);
        $headers = $headings[0][0] ?? [];

        // Read preview rows
        $rows = Excel::toArray(new class implements \Maatwebsite\Excel\Concerns\WithHeadingRow {
        }, $file);
        $preview = array_slice($rows[0] ?? [], 0, 5);

        // Auto-suggest column mapping
        $suggestedMapping = $this->suggestMapping($headers);

        Cache::put($fileKey, $path, now()->addMinutes(30));

        return response()->json([
            'file_key' => $fileKey,
            'headers' => $headers,
            'preview' => $preview,
            'suggested_mapping' => $suggestedMapping,
        ]);
    }

    public function import(Request $request): JsonResponse
    {
        $request->validate([
            'file_key' => 'required|string',
            'mapping' => 'required|array',
            'clear_existing' => 'boolean',
            'merge_existing' => 'boolean',
        ]);

        $fileKey = $request->input('file_key');
        $path = Cache::pull($fileKey);

        if (!$path) {
            return response()->json(['message' => 'Upload session expired. Please upload the file again.'], 422);
        }

        if ($request->boolean('clear_existing')) {
            ContentMigrationEntry::query()->delete();
        }

        $mapping = $request->input('mapping');
        $import = new ContentMigrationImport($mapping, $request->boolean('merge_existing'));

        Excel::import($import, $path, 'local');

        // Clean up temp file
        \Illuminate\Support\Facades\Storage::disk('local')->delete($path);

        return response()->json($import->getResults());
    }

    public function update(Request $request, ContentMigrationEntry $entry): JsonResponse
    {
        $entry->update($request->only([
            'original_url', 'original_parent', 'original_sub', 'original_sub_sub',
            'new_url', 'franchise_id', 'record_type', 'record_id', 'template_id',
            'category', 'page_id', 'status', 'text_migrated',
            'imagery_migrated', 'checked', 'priority', 'owner', 'comments',
            'website_section', 'redirect_url', 'metas_ready',
            'target_review_date', 'pageviews', 'avg_engagement', 'key_events',
        ]));

        $entry->load(['franchise', 'template']);

        return response()->json(new ContentMigrationEntryResource($entry));
    }

    public function bulkUpdate(Request $request): JsonResponse
    {
        $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'exists:content_migration_entries,id',
            'clear' => 'nullable|array',
            'clear.*' => 'string',
        ]);

        $allowedFields = [
            'record_type', 'franchise_id', 'template_id', 'status', 'category',
            'website_section', 'redirect_url', 'priority', 'owner',
            'text_migrated', 'imagery_migrated', 'checked',
            'metas_ready', 'target_review_date',
            'pageviews', 'avg_engagement', 'key_events',
        ];

        $ids = $request->input('ids');
        $updates = $request->only($allowedFields);

        // Fields explicitly nominated for clearing get nulled out alongside
        // (or instead of) the assignment values. We intersect with the
        // allow-list so the clear array can't reach arbitrary columns.
        $clearFields = array_values(array_intersect(
            (array) $request->input('clear', []),
            $allowedFields,
        ));
        foreach ($clearFields as $field) {
            $updates[$field] = null;
        }

        // Drop entries the caller didn't send AND didn't request a clear for.
        // (Without this, omitted keys still appear as null and overwrite data.)
        $payload = collect($updates)
            ->filter(fn ($value, $key) => in_array($key, $clearFields, true) || $value !== null)
            ->all();

        if (!empty($payload)) {
            ContentMigrationEntry::whereIn('id', $ids)->update($payload);
        }

        $matchResults = [];

        // Auto-match records when record_type is being set (not cleared).
        if (!empty($updates['record_type']) && !in_array('record_type', $clearFields, true)) {
            $entries = ContentMigrationEntry::whereIn('id', $ids)->get();
            $matchResults = $this->autoMatchRecords($entries, $updates['record_type']);
        }

        $entries = ContentMigrationEntry::whereIn('id', $ids)
            ->with(['franchise', 'template'])
            ->get();

        return response()->json([
            'entries' => ContentMigrationEntryResource::collection($entries),
            'match_results' => $matchResults,
        ]);
    }

    public function destroy(ContentMigrationEntry $entry): JsonResponse
    {
        $entry->delete();

        return response()->json(['message' => 'Entry deleted.']);
    }

    public function ownerOptions(): JsonResponse
    {
        $defaults = array_filter(['mtc', tenant('name')]);

        $existing = ContentMigrationEntry::query()
            ->whereNotNull('owner')
            ->where('owner', '!=', '')
            ->distinct()
            ->orderBy('owner')
            ->pluck('owner')
            ->all();

        $options = collect([...$defaults, ...$existing])
            ->map(fn ($v) => trim((string) $v))
            ->filter()
            ->unique()
            ->values()
            ->all();

        return response()->json($options);
    }

    public function sectionOptions(): JsonResponse
    {
        $defaults = [
            'Franchise', 'New Vehicle', 'Offer', 'Careers',
            'Search Results', 'Full Page Ad', 'Dealership', 'Valuation',
            'Generic content',
        ];

        $existing = ContentMigrationEntry::query()
            ->whereNotNull('website_section')
            ->where('website_section', '!=', '')
            ->distinct()
            ->orderBy('website_section')
            ->pluck('website_section')
            ->all();

        $options = collect([...$defaults, ...$existing])
            ->map(fn ($v) => trim((string) $v))
            ->filter()
            ->unique()
            ->values()
            ->all();

        return response()->json($options);
    }

    public function parentPageOptions(): JsonResponse
    {
        $pages = Page::query()
            ->select(['id', 'title', 'slug'])
            ->orderBy('title')
            ->get()
            ->map(fn ($page) => [
                'id' => $page->id,
                'title' => $page->title,
                'slug' => $page->slug,
            ]);

        return response()->json($pages);
    }

    public function searchRecords(Request $request): JsonResponse
    {
        $request->validate([
            'type' => 'required|string',
            'query' => 'required|string|min:2',
        ]);

        $type = $request->input('type');
        $query = $request->input('query');
        $modelClass = ContentMigrationEntry::resolveRecordClass($type);

        if (!$modelClass) {
            return response()->json([]);
        }

        $term = '%' . $query . '%';
        $results = $modelClass::query()
            ->where(function ($q) use ($term, $type) {
                $q->where('slug', 'like', $term);
                if (in_array($type, ['page'])) {
                    $q->orWhere('title', 'like', $term);
                } else {
                    $q->orWhere('name', 'like', $term);
                }
            })
            ->limit(20)
            ->get()
            ->map(fn ($record) => [
                'id' => $record->id,
                'name' => $record->name ?? $record->title ?? $record->slug,
                'url' => method_exists($record, 'urlPath') ? $record->urlPath() : null,
            ]);

        return response()->json($results);
    }

    public function fetchContent(Request $request, ContentMigrationProcessor $processor): JsonResponse
    {
        $request->validate(['entry_id' => 'required|exists:content_migration_entries,id']);

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));

        if (!in_array($entry->record_type, ['page', 'offer', 'new_car'], true)) {
            return response()->json(['message' => 'This record type cannot be processed yet.'], 422);
        }

        // Pages and new cars are template-driven; offers can fall back to
        // static fields, so template_id is only enforced for the first two.
        if (in_array($entry->record_type, ['page', 'new_car'], true) && empty($entry->template_id)) {
            return response()->json(['message' => 'Entry must have a template assigned.'], 422);
        }

        if (empty($entry->original_url)) {
            return response()->json(['message' => 'Entry must have an original URL.'], 422);
        }

        try {
            $content = $processor->fetchOriginContent($entry->original_url);
        } catch (\Exception $e) {
            return response()->json(['message' => 'Failed to fetch content: ' . $e->getMessage()], 422);
        }

        // Store fetched content on entry so the map step can use it server-side
        $entry->update([
            'data' => array_merge($entry->data ?? [], ['fetched_content' => $content]),
        ]);

        $aiPayload = $processor->buildOriginPayload($content);

        return response()->json([
            'content' => $content,
            'ai_payload' => $aiPayload,
            'entry' => new ContentMigrationEntryResource($entry),
        ]);
    }

    public function mapContent(Request $request, ContentMigrationProcessor $processor): JsonResponse
    {
        $request->validate([
            'entry_id' => 'required|exists:content_migration_entries,id',
            'cached_only' => 'boolean',
        ]);

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));
        $fetchedContent = $entry->data['fetched_content'] ?? null;

        if (!$fetchedContent) {
            return response()->json(['message' => 'No fetched content found. Please run the fetch step first.'], 422);
        }

        // Templateless offers use the synthesized offer-fields structure for
        // the visual mapper — there's no template-level cache to consult, so
        // we short-circuit and let the frontend fall through to the manual
        // mapper which will pull the structure from getTemplateStructure.
        if (empty($entry->template_id)) {
            return response()->json([
                'mapping' => null,
                'preview' => null,
                'cached' => false,
            ]);
        }

        $template = Template::findOrFail($entry->template_id);

        $templateStructure = $processor->getTemplateStructure($template);
        $cachedRules = $processor->getCachedMapping($template);

        $cachedOnly = $request->boolean('cached_only');

        // cached_only short-circuits when there's nothing cached, so we don't
        // burn an AI call just to discover the cache is empty.
        if ($cachedOnly && !$cachedRules) {
            return response()->json([
                'mapping' => null,
                'preview' => null,
                'cached' => false,
            ]);
        }

        try {
            if ($cachedRules) {
                $mapping = $processor->applyCachedRules($cachedRules, $fetchedContent, $templateStructure);
            } else {
                $mapping = $processor->generateMapping($fetchedContent, $templateStructure);
            }
        } catch (\Exception $e) {
            // applyCachedRules still calls the AI to apply rules consistently to
            // the new content. If AI is unavailable, treat the cache as a miss so
            // the frontend can fall back to the manual mapper instead of erroring.
            if ($cachedOnly) {
                Log::warning('Cached mapping skipped, AI unavailable: ' . $e->getMessage());
                return response()->json([
                    'mapping' => null,
                    'preview' => null,
                    'cached' => false,
                    'reason' => $e->getMessage(),
                ]);
            }
            return response()->json(['message' => 'Mapping failed: ' . $e->getMessage()], 422);
        }

        $preview = $processor->buildPreview($mapping, $templateStructure);

        return response()->json([
            'mapping' => $mapping,
            'preview' => $preview,
            'cached' => $cachedRules !== null,
        ]);
    }

    public function previewMapping(Request $request, ContentMigrationProcessor $processor): JsonResponse
    {
        $request->validate($this->processingRequestRules());

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));
        $mapping = $request->input('mapping', []);
        $createVersion = $request->boolean('create_version', false);
        $pageDetails = $request->input('page_details', []);
        $extraBlocks = $request->input('extra_blocks', []);

        try {
            $record = $processor->applyMapping($entry, $mapping, $createVersion, $pageDetails, $extraBlocks);
        } catch (\Exception $e) {
            return response()->json(['message' => 'Failed to build preview: ' . $e->getMessage()], 422);
        }

        // Mirror the metas_ready auto-flag from approveMapping so a previewed
        // entry is consistent with one that was approved end-to-end.
        $metaTitle = trim((string) ($pageDetails['meta_title'] ?? $mapping['_seo']['title'] ?? ''));
        $metaDescription = trim((string) ($pageDetails['meta_description'] ?? $mapping['_seo']['description'] ?? ''));
        if ($metaTitle !== '' || $metaDescription !== '') {
            $entry->forceFill(['metas_ready' => true])->save();
        }

        return response()->json($this->processingResponse($record));
    }

    public function approveMapping(Request $request, ContentMigrationProcessor $processor): JsonResponse
    {
        $request->validate(array_merge(
            $this->processingRequestRules(),
            ['mapping_rules' => 'nullable|array'],
        ));

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));
        $mapping = $request->input('mapping', []);
        $createVersion = $request->boolean('create_version', false);
        $pageDetails = $request->input('page_details', []);
        $extraBlocks = $request->input('extra_blocks', []);

        try {
            $record = $processor->applyMapping($entry, $mapping, $createVersion, $pageDetails, $extraBlocks);
        } catch (\Exception $e) {
            return response()->json(['message' => 'Failed to apply mapping: ' . $e->getMessage()], 422);
        }

        // If SEO title/description landed on the record (from page_details or
        // _seo mapping), mark metas_ready so the Stage 3 progress chip flips
        // automatically without the user having to toggle it.
        $metaTitle = trim((string) ($pageDetails['meta_title'] ?? $mapping['_seo']['title'] ?? ''));
        $metaDescription = trim((string) ($pageDetails['meta_description'] ?? $mapping['_seo']['description'] ?? ''));
        if ($metaTitle !== '' || $metaDescription !== '') {
            $entry->forceFill(['metas_ready' => true])->save();
        }

        // Cache the mapping rules for this template. We prefer the new
        // selector-based rules from the visual mapper; if absent (legacy clients
        // / repeatables-only entries), skip caching so we don't write a value
        // blob that can't be replayed deterministically.
        $rules = $request->input('mapping_rules');
        $template = $entry->template_id ? Template::find($entry->template_id) : null;
        if ($template && is_array($rules) && !empty($rules)) {
            ContentMigrationTemplateMapping::updateOrCreate(
                ['template_id' => $template->id],
                [
                    'content_structure' => $processor->getTemplateStructure($template),
                    'mapping_rules' => $rules,
                ]
            );
        }

        $entry->refresh()->load(['franchise', 'template']);

        return response()->json(array_merge(
            ['entry' => new ContentMigrationEntryResource($entry)],
            $this->processingResponse($record),
        ));
    }

    /**
     * Validation rules shared by previewMapping and approveMapping. The
     * page_details payload carries fields for all three record types — keys
     * that don't apply to the entry's record_type are simply ignored by the
     * processor.
     *
     * @return array<string, string|array<int, string>>
     */
    private function processingRequestRules(): array
    {
        return [
            'entry_id' => 'required|exists:content_migration_entries,id',
            'mapping' => 'present|array',
            'create_version' => 'boolean',
            'page_details' => 'nullable|array',
            // Shared / page fields
            'page_details.title' => 'nullable|string|max:255',
            'page_details.name' => 'nullable|string|max:255',
            'page_details.slug' => 'nullable|string|max:255',
            'page_details.status' => 'nullable|in:draft,published',
            'page_details.franchise_id' => 'nullable|integer|exists:franchises,id',
            'page_details.parent_id' => 'nullable|integer|exists:pages,id',
            'page_details.featured' => 'nullable|boolean',
            'page_details.meta_title' => 'nullable|string|max:255',
            'page_details.meta_description' => 'nullable|string|max:500',
            'page_details.thumbnail' => 'nullable|string|max:2048',
            'page_details.media' => 'nullable|array',
            'page_details.media.*' => 'integer|exists:media,id',
            // Offer / NewCar fields
            'page_details.published' => 'nullable|boolean',
            'page_details.published_at' => 'nullable|date',
            'page_details.unpublished_at' => 'nullable|date',
            'page_details.description' => 'nullable|string',
            'page_details.dealership_id' => 'nullable|integer|exists:dealerships,id',
            'page_details.make_id' => 'nullable|integer',
            'page_details.model_id' => 'nullable|integer',
            'page_details.template_id' => 'nullable|integer|exists:templates,id',
            'page_details.vehicle_type' => 'nullable|string|max:64',
            'page_details.derivative' => 'nullable|string|max:255',
            'page_details.trim' => 'nullable|string|max:255',
            'page_details.new_car_type' => 'nullable|string|max:255',
            'page_details.price' => 'nullable|numeric',
            'page_details.full_price' => 'nullable|numeric',
            'page_details.monthly_price' => 'nullable|numeric',
            'page_details.deposit' => 'nullable|numeric',
            'page_details.colour' => 'nullable|string|max:255',
            'page_details.engine_size_cc' => 'nullable|numeric',
            'page_details.co2' => 'nullable|numeric',
            'page_details.mpg' => 'nullable|numeric',
            'page_details.youtube_video' => 'nullable|string|max:2048',
            'page_details.brochure_url' => 'nullable|string|max:2048',
            'page_details.battery_range' => 'nullable|numeric',
            'page_details.battery_capacity_kwh' => 'nullable|numeric',
            'page_details.battery_usable_capacity_kwh' => 'nullable|numeric',
            'page_details.battery_charge_time' => 'nullable|string|max:64',
            'page_details.battery_quick_charge_time' => 'nullable|string|max:64',
            'page_details.battery_quick_charge_level' => 'nullable|numeric',
            'page_details.t_and_cs' => 'nullable|string',
            'page_details.label' => 'nullable|string|max:255',
            'page_details.excerpt' => 'nullable|string',
            'extra_blocks' => 'nullable|array',
            'extra_blocks.*.element_id' => 'required|integer|exists:content_elements,id',
            'extra_blocks.*.name' => 'required|string|max:255',
            'extra_blocks.*.repeatable' => 'nullable|boolean',
            'extra_blocks.*.position' => 'required|integer|min:0',
        ];
    }

    /**
     * Build the JSON response after a successful preview/approve. Keys are
     * kept page-centric (`page_id`, `page_title`) for backward compatibility
     * with the existing frontend, but they refer to the produced record of
     * any supported type.
     */
    private function processingResponse(\Illuminate\Database\Eloquent\Model $record): array
    {
        $siteUrl = match (true) {
            $record instanceof Page => Site::pageUrl($record, true),
            $record instanceof \Mtc\MercuryDataModels\VehicleOffer => Site::offerUrl($record, true),
            $record instanceof \Mtc\MercuryDataModels\NewCar => Site::newCarUrl($record, true),
            default => method_exists($record, 'urlPath') ? $record->urlPath() : null,
        };

        return [
            'page_id' => $record->getKey(),
            'page_title' => $record->title ?? $record->name ?? null,
            'preview_url' => $siteUrl,
            'site_url' => $siteUrl,
        ];
    }

    public function getTemplateStructure(Request $request, ContentMigrationProcessor $processor): JsonResponse
    {
        $request->validate(['entry_id' => 'required|exists:content_migration_entries,id']);

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));

        if (empty($entry->template_id)) {
            // Offers without a content template get the synthesized offer
            // structure (offer columns + finance fields) so the visual
            // mapper still has something to drive against.
            if ($entry->record_type === 'offer') {
                return response()->json([
                    'structure' => $processor->buildOfferFieldStructure(),
                    'template_name' => 'Offer fields',
                    'flexible' => false,
                ]);
            }
            return response()->json(['message' => 'Entry must have a template assigned.'], 422);
        }

        $template = Template::findOrFail($entry->template_id);
        $structure = $processor->getTemplateStructure($template);
        $flexible = (bool) ($template->meta['flexible'] ?? false);

        // For templated offers we APPEND the synthesized offer-field +
        // finance sections so the user can map both template content AND
        // the offer's direct columns / finance row in a single pass.
        if ($entry->record_type === 'offer') {
            $structure = array_merge($structure, $processor->buildOfferFieldStructure());
        }

        return response()->json([
            'structure' => $structure,
            'template_name' => $template->name,
            'flexible' => $flexible,
        ]);
    }

    public function rejectMapping(Request $request): JsonResponse
    {
        $request->validate([
            'entry_id' => 'required|exists:content_migration_entries,id',
            'notes' => 'required|string',
        ]);

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));
        $entry->update(['process_notes' => $request->input('notes')]);
        $entry->load(['franchise', 'template']);

        return response()->json(new ContentMigrationEntryResource($entry));
    }

    public function confirmProcessed(Request $request): JsonResponse
    {
        $request->validate(['entry_id' => 'required|exists:content_migration_entries,id']);

        $entry = ContentMigrationEntry::findOrFail($request->input('entry_id'));
        $entry->load(['franchise', 'template']);

        return response()->json(new ContentMigrationEntryResource($entry));
    }

    public function nextProcessable(Request $request): JsonResponse
    {
        $afterId = (int) $request->input('after_id', 0);

        $next = ContentMigrationEntry::query()
            ->where('status', 'keep')
            ->whereNotNull('original_url')
            ->whereNull('processed_at')
            ->where(function ($q) {
                $q->where(function ($sub) {
                    $sub->whereIn('record_type', ['page', 'new_car'])
                        ->whereNotNull('template_id');
                })->orWhere('record_type', 'offer');
            })
            ->when($afterId > 0, fn ($q) => $q->where('id', '!=', $afterId))
            ->orderBy('id')
            ->first();

        return response()->json([
            'entry_id' => $next?->id,
        ]);
    }

    public function uploadSitemap(Request $request): JsonResponse
    {
        $request->validate([
            'file' => 'required|file|mimes:xml',
            'clear_existing' => 'boolean',
        ]);

        $file = $request->file('file');
        $importKey = 'content-migration-import-' . Str::random(16);
        $path = $file->storeAs('temp', $importKey . '.xml', 'local');

        Cache::put($importKey, ['status' => 'queued', 'processed' => 0, 'imported' => 0, 'total' => 0], now()->addMinutes(30));

        ImportSitemapJob::dispatch($path, false, $importKey, $request->boolean('clear_existing'));

        return response()->json(['import_key' => $importKey]);
    }

    public function importSitemapUrl(Request $request): JsonResponse
    {
        $request->validate([
            'url' => 'required|url',
            'clear_existing' => 'boolean',
        ]);

        $importKey = 'content-migration-import-' . Str::random(16);

        Cache::put($importKey, ['status' => 'queued', 'processed' => 0, 'imported' => 0, 'total' => 0], now()->addMinutes(30));

        ImportSitemapJob::dispatch($request->input('url'), true, $importKey, $request->boolean('clear_existing'));

        return response()->json(['import_key' => $importKey]);
    }

    public function startCrawl(Request $request): JsonResponse
    {
        $request->validate([
            'url' => 'required|url',
            'clear_existing' => 'boolean',
        ]);

        $importKey = 'content-migration-import-' . Str::random(16);

        Cache::put($importKey, [
            'status' => 'queued',
            'crawled' => 0,
            'imported' => 0,
        ], now()->addMinutes(30));

        CrawlSiteJob::dispatch($request->input('url'), $importKey, $request->boolean('clear_existing'));

        return response()->json(['import_key' => $importKey]);
    }

    public function importStatus(Request $request): JsonResponse
    {
        $request->validate([
            'import_key' => 'required|string',
        ]);

        $status = Cache::get($request->input('import_key'));

        if (!$status) {
            return response()->json(['status' => 'unknown'], 404);
        }

        return response()->json($status);
    }

    private function suggestMapping(array $headers): array
    {
        $fieldMap = [
            'original_url' => ['url', 'original_url', 'original url', 'page url', 'current url', 'old url', 'source url'],
            'original_parent' => ['parent', 'parent_page', 'parent page', 'section', 'category'],
            'original_sub' => ['sub', 'sub_page', 'sub page', 'child', 'sub section'],
            'original_sub_sub' => ['sub_sub', 'sub sub', 'sub_sub_page', 'sub sub page'],
            'new_url' => ['new_url', 'new url', 'destination', 'target url', 'new page url'],
            'franchise' => ['franchise', 'brand', 'dealer group'],
            'template' => ['template', 'page template', 'layout'],
            'record_type' => ['type', 'record_type', 'record type', 'page type', 'content type'],
            'status' => ['status', 'migration status'],
            'text_migrated' => ['text', 'text_migrated', 'text migrated', 'copy migrated', 'content migrated'],
            'imagery_migrated' => ['imagery', 'imagery_migrated', 'imagery migrated', 'images', 'images migrated'],
            'checked' => ['checked', 'reviewed', 'verified', 'qa'],
            'priority' => ['priority', 'importance', 'order'],
            'owner' => ['owner', 'assigned', 'assigned to', 'assignee', 'responsible'],
            'comments' => ['comments', 'notes', 'description', 'remarks'],
            'website_section' => ['website section', 'website_section', 'section', 'site section'],
            'redirect_url' => ['redirect url', 'redirect_url', 'redirect to', 'redirect destination'],
            'metas_ready' => ['metas ready', 'metas_ready', 'meta ready', 'seo ready', 'meta'],
            'target_review_date' => ['target review date', 'target_review_date', 'review date', 'target date', 'review by'],
            'pageviews' => ['pageviews', 'page views', 'views', 'pageview', 'screen page views', 'sessions'],
            'avg_engagement' => ['avg engagement', 'avg_engagement', 'avg engagement time', 'average engagement', 'average engagement time', 'engagement time', 'engagement'],
            'key_events' => ['key events', 'key_events', 'conversions', 'goals', 'goal completions'],
        ];

        $mapping = [];

        foreach ($headers as $header) {
            $normalised = strtolower(trim(str_replace(['_', '-'], ' ', $header)));

            foreach ($fieldMap as $field => $aliases) {
                if (in_array($normalised, $aliases)) {
                    $mapping[$header] = $field;
                    break;
                }
            }
        }

        return $mapping;
    }

    private function autoMatchRecords($entries, string $recordType): array
    {
        $modelClass = ContentMigrationEntry::resolveRecordClass($recordType);

        if (!$modelClass) {
            return [];
        }

        $nameField = $recordType === 'page' ? 'title' : 'name';
        $allRecords = $modelClass::all();

        $results = [];

        foreach ($entries as $entry) {
            $searchTerms = array_filter([
                $entry->original_url,
                $entry->new_url,
                $entry->original_parent,
                $entry->original_sub,
            ]);

            if (empty($searchTerms)) {
                $results[$entry->id] = ['matched' => false];
                continue;
            }

            $match = null;

            // Try matching by slug from URL
            foreach ($searchTerms as $term) {
                $slug = Str::slug(basename(rtrim($term, '/')));
                if (empty($slug)) {
                    continue;
                }

                $match = $allRecords->first(fn ($r) => $r->slug === $slug);
                if ($match) {
                    break;
                }
            }

            // Try fuzzy match on name/title
            if (!$match) {
                foreach ($searchTerms as $term) {
                    $cleaned = strtolower(trim(basename(rtrim($term, '/'))));
                    $cleaned = str_replace(['-', '_'], ' ', $cleaned);
                    if (empty($cleaned)) {
                        continue;
                    }

                    $match = $allRecords->first(
                        fn ($r) => strtolower($r->{$nameField} ?? '') === $cleaned
                    );
                    if ($match) {
                        break;
                    }
                }
            }

            if ($match) {
                $entry->update(['record_id' => $match->id]);
                $results[$entry->id] = [
                    'matched' => true,
                    'record_id' => $match->id,
                    'record_name' => $match->{$nameField} ?? $match->slug,
                ];
            } else {
                $results[$entry->id] = ['matched' => false];
            }
        }

        return $results;
    }
}
