<?php

namespace FleekDash\Controllers;

use FleekDash\Services\ResponseService;
use FleekDash\Services\Theme\ThemeService;
use InvalidArgumentException;
use WP_REST_Request;
use WP_REST_Response;

defined('ABSPATH') || exit;

/**
 * ThemesController
 *
 * REST endpoints for FleekDash theme and palette management.
 */
class ThemesController
{
    private ThemeService $themeService;

    public function __construct(?ThemeService $themeService = null)
    {
        $this->themeService = $themeService ?? new ThemeService();
    }

    /**
     * GET /themes
     */
    public function list(WP_REST_Request $_request) // NOSONAR php:S1172 - WP REST callback
    {
        $payload = $this->themeService->getThemesListPayload();

        return ResponseService::success([
            'themes' => $payload['themes'],
            'active' => $payload['active'],
        ]);
    }

    /**
     * GET /themes/{id}
     */
    public function get(WP_REST_Request $request)
    {
        $themeId = $request->get_param('themeId') ?? $request->get_param('id') ?? '';
        $theme = $this->themeService->getTheme($themeId);

        if (!$theme) {
            return ResponseService::error('Theme not found.', null, 404);
        }

        return ResponseService::success([
            'theme' => $theme,
        ]);
    }

    /**
     * GET /themes/active
     */
    public function active(WP_REST_Request $_request) // NOSONAR php:S1172 - WP REST callback
    {
        $ctx = $this->themeService->getActiveThemeContext();

        return ResponseService::success([
            'settings' => $ctx['settings'],
            'theme' => $ctx['theme'],
            'palette' => $ctx['palette'],
        ]);
    }

    /**
     * POST /themes/apply
     */
    public function apply(WP_REST_Request $request)
    {
        $payload = $request->get_json_params() ?? [];
        $themeId = $payload['themeId'] ?? $payload['theme_id'] ?? $request->get_param('themeId') ?? $request->get_param('theme_id') ?? '';
        $paletteId = $payload['paletteId'] ?? $payload['palette_id'] ?? $request->get_param('paletteId') ?? $request->get_param('palette_id') ?? '';

        try {
            $result = $this->themeService->applyActiveTheme($themeId, $paletteId);
        } catch (InvalidArgumentException $exception) {
            return ResponseService::error($exception->getMessage(), null, 400);
        }

        return ResponseService::success('Theme applied successfully.', [
            'settings' => [
                'theme' => $result['theme'],
                'palette' => $result['palette'],
            ],
            'theme' => $result['theme_data'],
            'palette' => $result['palette_data'],
        ]);
    }

    /**
     * POST /themes
     */
    public function create(WP_REST_Request $request)
    {
        $payload = $request->get_json_params() ?? [];

        try {
            $theme = $this->themeService->saveCustomTheme($payload);
        } catch (InvalidArgumentException $exception) {
            error_log('[ThemesController::create] Exception: ' . $exception->getMessage());
            return ResponseService::error($exception->getMessage(), null, 400);
        }

        return ResponseService::success('Theme saved successfully.', [
            'theme' => $theme,
        ]);
    }

    /**
     * PUT /themes/{id}
     */
    public function update(WP_REST_Request $request)
    {
        $pathId = $request->get_param('themeId') ?? $request->get_param('id') ?? '';
        $payload = $request->get_json_params() ?? [];
        $payload['id'] = $payload['id'] ?? $pathId;

        try {
            $theme = $this->themeService->saveCustomTheme($payload);
        } catch (InvalidArgumentException $exception) {
            return ResponseService::error($exception->getMessage(), null, 400);
        }

        return ResponseService::success('Theme updated successfully.', [
            'theme' => $theme,
        ]);
    }

    /**
     * DELETE /themes/{id}
     */
    public function delete(WP_REST_Request $request)
    {
        $themeId = $request->get_param('themeId') ?? $request->get_param('id') ?? '';

        try {
            $this->themeService->deleteCustomTheme($themeId);
        } catch (InvalidArgumentException $exception) {
            return ResponseService::error($exception->getMessage(), null, 400);
        }

        return ResponseService::success('Theme deleted successfully.', [
            'theme_id' => \sanitize_key($themeId),
        ]);
    }

    /**
     * POST /themes/export/{id}
     */
    public function export(WP_REST_Request $request)
    {
        $themeId = $request->get_param('themeId') ?? $request->get_param('id') ?? '';
        $theme = $this->themeService->getTheme($themeId);

        if (!$theme) {
            return ResponseService::error('Theme not found.', null, 404);
        }

        $palettes = [];
        foreach ($theme['palettes'] ?? [] as $palette) {
            if (is_array($palette)) {
                $palettes[] = $palette;
            } elseif (is_string($palette)) {
                $paletteData = $this->themeService->getPalette($palette);
                if ($paletteData) {
                    $palettes[] = $paletteData;
                }
            }
        }

        $payload = [
            'meta' => [
                'exported_at' => \current_time('mysql'),
                'fleekdash_version' => defined('FLEEKDASH_VERSION') ? FLEEKDASH_VERSION : '2.x',
            ],
            'theme' => $theme,
            'palettes' => $palettes,
        ];

        $response = new WP_REST_Response($payload);
        $response->set_status(200);
        $response->set_headers([
            'Content-Type' => 'application/json',
            'Content-Disposition' => sprintf(
                'attachment; filename="%s-theme.json"',
                sanitize_file_name($theme['id'] ?? 'fleekdash-theme'),
            ),
        ]);

        return $response;
    }

    /**
     * POST /themes/import
     */
    public function import(WP_REST_Request $request) // NOSONAR php:S1142 - multiple returns for validation
    {
        $fileError = $this->validateImportFile($request);
        if ($fileError !== null) {
            return $fileError;
        }

        $file = $request->get_file_params()['theme'] ?? null;
        $contents = file_get_contents($file['tmp_name']);
        if ($contents === false) {
            return ResponseService::error('Unable to read uploaded theme file.', null, 400);
        }

        $data = json_decode($contents, true);
        if (!is_array($data) || !isset($data['theme'])) {
            return ResponseService::error('Invalid theme export file.', null, 400);
        }

        $themePayload = $data['theme'];
        $palettesPayload = is_array($data['palettes'] ?? null) ? $data['palettes'] : [];

        try {
            $importResult = $this->importThemePayload($themePayload, $palettesPayload);
            return ResponseService::success('Theme imported successfully.', $importResult);
        } catch (InvalidArgumentException $exception) {
            return ResponseService::error($exception->getMessage(), null, 400);
        }
    }

    private function validateImportFile(WP_REST_Request $request): ?\WP_REST_Response
    {
        $files = $request->get_file_params();
        $file = $files['theme'] ?? null;

        if (!$file || !isset($file['tmp_name']) || !file_exists($file['tmp_name'])) {
            return ResponseService::error('Theme file is required for import.', null, 400);
        }
        if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
            return ResponseService::error('Failed to upload theme file.', null, 400);
        }

        return null;
    }

    /**
     * Import payload helper.
     *
     * @param array<string, mixed> $themePayload
     * @param array<int, array<string, mixed>> $palettesPayload
     * @return array<string, mixed>
     */
    private function importThemePayload(array $themePayload, array $palettesPayload): array
    {
        $originalThemeId = \sanitize_key($themePayload['id'] ?? '');
        $themeId = $this->resolveImportThemeId($originalThemeId);

        [$paletteIdMap, $importedPalettes] = $this->importPalettesAndBuildMap($palettesPayload, $themeId);

        $normalizedPalettes = $this->normalizeThemePaletteRefs($themePayload['palettes'] ?? [], $paletteIdMap);
        $defaultPalette = $this->resolveDefaultPalette($themePayload, $normalizedPalettes, $paletteIdMap);

        $themePayload['id'] = $themeId;
        $themePayload['palettes'] = $normalizedPalettes;
        $themePayload['default_palette'] = $defaultPalette;
        $themePayload['parent_theme'] = \sanitize_key($themePayload['parent_theme'] ?? 'starter');
        $themePayload['source'] = 'custom';

        $importedTheme = $this->themeService->saveCustomTheme($themePayload);

        return [
            'theme' => $importedTheme,
            'palettes' => $importedPalettes,
            'original_theme_id' => $originalThemeId,
        ];
    }

    private function resolveImportThemeId(string $originalThemeId): string
    {
        $themeId = $originalThemeId !== '' ? $originalThemeId : 'custom-theme';
        if ($this->themeService->getTheme($themeId)) {
            $themeId = $this->generateUniqueId($themeId, fn($c) => (bool) $this->themeService->getTheme($c));
        }
        return $themeId;
    }

    /**
     * @param array<int, array<string, mixed>> $palettesPayload
     * @return array{0: array<string, string>, 1: array<int, array<string, mixed>>}
     */
    private function importPalettesAndBuildMap(array $palettesPayload, string $themeId): array
    {
        $paletteIdMap = [];
        $importedPalettes = [];

        foreach ($palettesPayload as $palette) {
            if (!is_array($palette)) {
                continue;
            }
            $originalId = \sanitize_key($palette['id'] ?? '');
            if ($originalId === '') {
                continue;
            }

            $newId = $this->themeService->paletteExists($originalId)
                ? $this->generateUniqueId($originalId, fn($c) => $this->themeService->paletteExists($c))
                : $originalId;

            $paletteIdMap[$originalId] = $newId;
            $palette['id'] = $newId;
            $palette['theme'] = $themeId;
            $palette['source'] = 'custom';

            $importedPalettes[] = $this->themeService->saveCustomPalette($palette);
        }

        return [$paletteIdMap, $importedPalettes];
    }

    /**
     * @param array<int, mixed> $themePalettes
     * @param array<string, string> $paletteIdMap
     * @return array<int, string>
     */
    private function normalizeThemePaletteRefs(array $themePalettes, array $paletteIdMap): array
    {
        $normalized = [];
        if (!is_array($themePalettes)) {
            return $normalized;
        }
        foreach ($themePalettes as $paletteId) {
            $mappedId = $paletteIdMap[\sanitize_key($paletteId)] ?? \sanitize_key($paletteId);
            if ($mappedId !== '') {
                $normalized[] = $mappedId;
            }
        }
        return $normalized;
    }

    /**
     * @param array<string, mixed> $themePayload
     * @param array<int, string> $normalizedPalettes
     * @param array<string, string> $paletteIdMap
     */
    private function resolveDefaultPalette(array $themePayload, array $normalizedPalettes, array $paletteIdMap): string
    {
        $defaultPalette = \sanitize_key($themePayload['default_palette'] ?? '');
        if ($defaultPalette !== '') {
            return $paletteIdMap[$defaultPalette] ?? $defaultPalette;
        }
        return !empty($normalizedPalettes) ? $normalizedPalettes[0] : '';
    }

    /**
     * Generate unique ID for themes/palettes during import.
     *
     * @param callable(string):bool $existsCallback
     */
    private function generateUniqueId(string $baseId, callable $existsCallback): string
    {
        $baseId = \sanitize_key($baseId);
        if ($baseId === '') {
            $baseId = 'imported-item';
        }

        $candidate = $baseId;
        $suffix = 1;

        while ($existsCallback($candidate)) {
            $candidate = $baseId . '-' . $suffix;
            $suffix++;
        }

        return $candidate;
    }
}
