<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreImportMap;
use App\Http\Requests\UpdateImportMap;
use App\Http\Resources\ImportMapResource;
use App\ImportMaps\VehicleImportMap;
use App\IntegrationRepository;
use App\Models\ImportMap;
use Box\Spout\Common\Entity\Cell;
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Facades\Settings;

class ImportMapController extends Controller
{
    public function index()
    {
        return ImportMap::query()->paginate();
    }

    public function store(StoreImportMap $request): ImportMapResource
    {
        return new ImportMapResource(ImportMap::query()
            ->create([
                'name' => $request->input('name'),
                'type' => $request->input('type'),
            ]));
    }

    public function show(ImportMap $importMap): ImportMapResource
    {
        return new ImportMapResource($importMap);
    }

    public function update(UpdateImportMap $request, ImportMap $importMap): ImportMapResource
    {
        $data = $request->input('data') ?? [];
        $data['has_header_row'] = $request->input('has_header_row');
        $data['clear_previous_entries'] = $request->input('clear_previous_entries');
        $data['remove_from_remote'] = $request->input('remove_from_remote');
        $data['extra_fields'] = collect($data['extra_fields'] ?? [])
            ->filter(fn ($row) => !empty($row['on_model']))
            ->toArray();
        $importMap->update([
            'name' => $request->input('name'),
            'data' => $data,
        ]);
        foreach ($request->input('fields') as $field) {
            if (empty($field['on_file']) && $field['on_file'] !== '0') {
                continue;
            }
            $data = $field['data'] ?? [];
            $data['required_to_import'] = !empty($field['required_to_import']);
            $data['save_on_update'] = !empty($field['save_on_update']);
            $castRestriction = $this->getCastRestriction($importMap, $field['on_model']);
            if ($castRestriction) {
                $data['cast_type'] = $castRestriction;
            }
            $importMap->fields()
                ->updateOrCreate([
                    'on_file' => $field['on_file'],
                ], [
                    'on_model' => $field['on_model'],
                    'is_match_key' => !empty($field['is_match_key']),
                    'default_value' => $field['default_value'],
                    'is_images' => $field['on_model'] === 'mediaUses',
                    'is_list' => $field['is_list'],
                    'list_separator' => $field['list_separator'],
                    'data' => $data,
                ]);
        }
        $extraFieldIds = collect($request->input('data.extra_fields', []))
            ->pluck('id')
            ->filter()
            ->values();
        $importMap->fields()
            ->where('data->is_static', true)
            ->whereNotIn('on_file', $extraFieldIds)
            ->delete();
        foreach ($request->input('data.extra_fields') as $field) {
            if (empty($field['on_model'])) {
                continue;
            }
            $data = $field['data'] ?? [];
            $data['is_static'] = true;
            $data['save_on_update'] = !empty($field['save_on_update']);
            $castRestriction = $this->getCastRestriction($importMap, $field['on_model']);
            if ($castRestriction) {
                $data['cast_type'] = $castRestriction;
            }
            $importMap->fields()
                ->updateOrCreate([
                    'on_file' => $field['id'],
                ], [
                    'on_model' => $field['on_model'],
                    'is_match_key' => !empty($field['is_match_key']),
                    'default_value' => $field['default_value'] ?? null,
                    'is_images' => $field['on_model'] === 'mediaUses',
                    'is_list' => $field['is_list'] ?? false,
                    'list_separator' => $field['list_separator'] ?? null,
                    'data' => $data,
                ]);
        }

        if ($data['automate'] ?? false) {
            Settings::update('stock-file-enabled', true);
        }

        return new ImportMapResource($importMap);
    }

    public function upload(Request $request, ImportMap $importMap)
    {
        $data = $importMap->data ?? [];
        $uploaded = $request->file('file');
        $extension = strtolower($uploaded->getClientOriginalExtension());

        if ($extension === 'json') {
            $data = $this->extractJsonSample($uploaded, $data);
        } else {
            $data['row_data'] = $this->extractSpreadsheetSample($uploaded, (bool) $request->input('has_header_row'));
        }

        $importMap->update([
            'data' => $data
        ]);

        $importMap->fields()
            ->whereNotIn('on_file', array_keys($data['row_data'] ?? []))
            ->delete();

        foreach (($data['row_data'] ?? []) as $column => $value) {
            $importMap->fields()->updateOrCreate([
                'on_file' => $column
            ]);
        }
        return new ImportMapResource($importMap);
    }

    /**
     * Parse the first row from a CSV/XLS/XLSX sample (delegates to box/spout).
     */
    private function extractSpreadsheetSample($uploaded, bool $hasHeaderRow): array
    {
        $uploaded->storeAs($uploaded->getClientOriginalName());
        $reader = ReaderEntityFactory::createReaderFromFile($uploaded->getClientOriginalName());
        $reader->open($uploaded);

        $rowData = [];
        $headerColumns = collect();
        foreach ($reader->getSheetIterator() as $sheet) {
            foreach ($sheet->getRowIterator() as $index => $row) {
                if ($index == 1) {
                    $headerColumns = $hasHeaderRow
                        ? collect($row->getCells())->map(fn (Cell $cell) => Str::slug($cell->getValue(), '_'))
                        : collect($row->getCells())->map(fn (Cell $cell, $i) => $i);
                    Log::debug('header row', [$headerColumns]);
                } else {
                    $rowData = collect($row->getCells())
                        ->keyBy(fn ($cell, $i) => $headerColumns[$i] ?? $i)
                        ->map(fn (Cell $cell) => $cell->getValue())
                        ->toArray();
                }
                if ($index > 2) {
                    break;
                }
            }
        }
        return $rowData;
    }

    /**
     * Parse a sample JSON payload (e.g. a saved API response) and discover
     * the array of rows. When the import map has data.http.array_path set,
     * that path is honoured. Otherwise we walk the payload to find the
     * first list of objects and persist the discovered path back into the
     * map so the runtime fetch uses the same location.
     */
    private function extractJsonSample($uploaded, array $data): array
    {
        $payload = json_decode((string) file_get_contents($uploaded->getRealPath()), true);
        if (!is_array($payload)) {
            throw new \Exception('Uploaded file is not valid JSON or is empty.');
        }

        $configuredPath = $data['http']['array_path'] ?? null;
        $rows = null;
        $discoveredPath = null;

        if (!empty($configuredPath)) {
            $rows = \Illuminate\Support\Arr::get($payload, $configuredPath);
        } elseif (array_is_list($payload)) {
            $rows = $payload;
        } else {
            [$rows, $discoveredPath] = $this->findFirstArrayOfObjects($payload);
        }

        if (!is_array($rows) || empty($rows)) {
            throw new \Exception('No array of rows found in JSON payload. Set "Array path" on the HTTP config if the list lives under a specific key.');
        }

        $firstRow = $rows[array_key_first($rows)] ?? null;
        if (!is_array($firstRow)) {
            throw new \Exception('Expected the JSON list to contain objects/rows.');
        }

        if ($discoveredPath !== null && empty($configuredPath)) {
            $data['http'] = ($data['http'] ?? []) + ['url' => '', 'headers' => [], 'latest_by' => ''];
            $data['http']['array_path'] = $discoveredPath;
        }

        $data['row_data'] = collect($firstRow)
            ->map(fn ($value) => is_scalar($value) ? (string) $value : json_encode($value))
            ->toArray();

        return $data;
    }

    /**
     * Walk top-level keys of a payload looking for the first non-empty
     * array whose entries are themselves objects/arrays — i.e. the list
     * of rows. Returns [rows, discoveredPath] or [null, null].
     *
     * @return array{0: ?array, 1: ?string}
     */
    private function findFirstArrayOfObjects(array $payload): array
    {
        foreach ($payload as $key => $value) {
            if (!is_array($value) || empty($value)) {
                continue;
            }
            $first = $value[array_key_first($value)] ?? null;
            if (is_array($first)) {
                return [array_values($value), (string) $key];
            }
        }
        return [null, null];
    }

    public function download(ImportMap $importMap, $file)
    {
        $disk = Storage::build([
            'driver' => $importMap['data']['protocol'],
            'root' => $importMap['data']['file_root'] ?? null,
            'host' => $importMap['data']['host'],
            'username' => $importMap['data']['username'],
            'password' => $importMap['data']['password'],
            'throw' => false,
        ]);

        return $disk->download($file, basename((string) $file), [
            'Content-Type' => $disk->mimeType($file),
        ]);
    }

    public function destroy(ImportMap $importMap)
    {
        $importMap->delete();

        return response('ok');
    }

    public function getForModel(string $model): Response
    {
        return response([
            'automations' => $this->getImportAutomations($model),
            'types' => ImportMap::query()
                ->where('type', $model)
                ->select('id', 'name')
                ->get()
                ->prepend([
                    'id' => 'default',
                    'name' => 'Autonomy import file'
                ])
        ]);
    }

    private function getCastRestriction(ImportMap $importMap, ?string $field): ?string
    {
        if (empty($field)) {
            return null;
        }

        return match ($importMap->type) {
            'vehicle' => (new VehicleImportMap())->castRestrictions($field),
            default => null,
        };
    }

    private function getImportAutomations(string $model)
    {
        if ($model == 'vehicle') {
            return new IntegrationRepository()->getAutomationsForVehicle();
        }
        return [];
    }
}
