<?php

namespace FSPoster\App\Watermark\App\Mappers;

use Exception;
use FSPoster\App\Providers\Core\RestRequest;
use FSPoster\App\Watermark\App\DTOs\Request\ImageWatermarkOptions;
use FSPoster\App\Watermark\App\DTOs\Request\SaveWatermarkRequest;
use FSPoster\App\Watermark\App\DTOs\Request\TextWatermarkOptions;
use FSPoster\App\Watermark\App\DTOs\Request\WatermarkFilters;
use FSPoster\App\Watermark\App\DTOs\Request\WatermarkOptions;
use FSPoster\App\Watermark\App\DTOs\Request\WatermarkRule;

class SaveWatermarkRequestMapper
{
    /**
     * @throws Exception
     */
    public function fromRequest(RestRequest $request): SaveWatermarkRequest
    {
        $dto = new SaveWatermarkRequest();
        $dto->watermarkEnabled = (bool) $request->param('watermark_enabled', false, RestRequest::TYPE_BOOL);
        $dto->watermarkRenderingFails = $request->param(
            'watermark_rendering_fails',
            SaveWatermarkRequest::RENDERING_FAILS_ORIGINAL,
            RestRequest::TYPE_STRING,
            SaveWatermarkRequest::RENDERING_FAILS_OPTIONS
        );

        $rulesJson = $request->param('rules', '[]', RestRequest::TYPE_STRING);
        $dto->rules = $this->mapRules($rulesJson);

        return $dto;
    }

    /**
     * @return WatermarkRule[]
     * @throws Exception
     */
    private function mapRules(string $rulesJson): array
    {
        $decoded = json_decode($rulesJson, true);

        if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
            throw new Exception(fsp__('Invalid rules data'));
        }

        $rules = [];
        $seenIds = [];
        foreach ($decoded as $raw) {
            if (!is_array($raw)) {
                throw new Exception(fsp__('Invalid rules data'));
            }

            $rule = $this->mapRule($raw);

            if (isset($seenIds[$rule->id])) {
                throw new Exception(fsp__('Id already exists'));
            }
            $seenIds[$rule->id] = true;

            $rules[] = $rule;
        }

        return $rules;
    }

    /**
     * @throws Exception
     */
    private function mapRule(array $raw): WatermarkRule
    {
        $rule = new WatermarkRule();

        $id = $raw['id'] ?? '';
        if (!is_string($id) || $id === '') {
            throw new Exception(fsp__('Id field must be provided'));
        }
        $rule->id = $id;

        $name = $raw['name'] ?? '';
        if (!is_string($name) || $name === '') {
            throw new Exception(fsp__('Name field must be provided'));
        }
        $rule->name = $name;

        if (!array_key_exists('enabled', $raw)) {
            throw new Exception(fsp__('Enabled field must be provided'));
        }
        $rule->enabled = (bool) $raw['enabled'];

        $applyType = $raw['applyType'] ?? '';
        if (!is_string($applyType) || $applyType === '') {
            throw new Exception(fsp__('Apply type field must be provided'));
        }
        if (!in_array($applyType, WatermarkRule::APPLY_TYPES, true)) {
            throw new Exception(fsp__('Apply type can only be image or text'));
        }
        $rule->applyType = $applyType;

        $filters = $raw['filters'] ?? null;
        if (!is_array($filters) || empty($filters)) {
            throw new Exception(fsp__('Filters field must be provided'));
        }
        $rule->filters = $this->mapFilters($filters);

        $options = $raw['options'] ?? null;
        if (!is_array($options)) {
            throw new Exception(fsp__('Options field must be provided'));
        }

        $rule->options = $applyType === WatermarkRule::APPLY_TYPE_IMAGE
            ? $this->mapImageOptions($options)
            : $this->mapTextOptions($options);

        return $rule;
    }

    private function mapFilters(array $raw): WatermarkFilters
    {
        $filters = new WatermarkFilters();
        $filters->mediaTypes = $this->toStringArray($raw['mediaTypes'] ?? []);
        $filters->socialNetworks = $this->toStringArray($raw['socialNetworks'] ?? []);

        return $filters;
    }

    /**
     * @throws Exception
     */
    private function mapImageOptions(array $raw): ImageWatermarkOptions
    {
        $position = is_string($raw['position'] ?? null) ? $raw['position'] : '';
        if (!in_array($position, WatermarkOptions::POSITIONS, true)) {
            throw new Exception(fsp__('Position field is invalid'));
        }

        $vertical = (int) ($raw['vertical'] ?? 0);
        if ($vertical < 0 || $vertical > 100) {
            throw new Exception(fsp__('Vertical must be between 0 and 100'));
        }

        $horizontal = (int) ($raw['horizontal'] ?? 0);
        if ($horizontal < 0 || $horizontal > 100) {
            throw new Exception(fsp__('Horizontal must be between 0 and 100'));
        }

        if (!isset($raw['imageOpacity'])) {
            throw new Exception(fsp__('Image opacity must be provided'));
        }
        $imageOpacity = (int) $raw['imageOpacity'];
        if ($imageOpacity < 0 || $imageOpacity > 100) {
            throw new Exception(fsp__('Image opacity must be between 0 and 100'));
        }

        if (!isset($raw['imageSize'])) {
            throw new Exception(fsp__('Image size must be provided'));
        }
        $imageSize = (int) $raw['imageSize'];
        if ($imageSize < 0 || $imageSize > 100) {
            throw new Exception(fsp__('Image size must be between 0 and 100'));
        }

        $imageUrl = isset($raw['imageUrl']) && is_string($raw['imageUrl']) && $raw['imageUrl'] !== ''
            ? $raw['imageUrl']
            : null;
        if ($imageUrl === null) {
            throw new Exception(fsp__('Image URL must be provided'));
        }

        $options = new ImageWatermarkOptions();
        $options->position = $position;
        $options->vertical = $vertical;
        $options->horizontal = $horizontal;
        $options->imageId = isset($raw['imageId']) && is_numeric($raw['imageId']) ? (int) $raw['imageId'] : null;
        $options->imageUrl = $imageUrl;
        $options->imageSize = $imageSize;
        $options->imageOpacity = $imageOpacity;

        return $options;
    }

    /**
     * @throws Exception
     */
    private function mapTextOptions(array $raw): TextWatermarkOptions
    {
        $position = is_string($raw['position'] ?? null) ? $raw['position'] : '';
        if (!in_array($position, WatermarkOptions::POSITIONS, true)) {
            throw new Exception(fsp__('Position field is invalid'));
        }

        $vertical = (int) ($raw['vertical'] ?? 0);
        if ($vertical < 0 || $vertical > 100) {
            throw new Exception(fsp__('Vertical must be between 0 and 100'));
        }

        $horizontal = (int) ($raw['horizontal'] ?? 0);
        if ($horizontal < 0 || $horizontal > 100) {
            throw new Exception(fsp__('Horizontal must be between 0 and 100'));
        }

        $textValue = is_string($raw['textValue'] ?? null) ? $raw['textValue'] : '';
        if ($textValue === '') {
            throw new Exception(fsp__('Text value must be provided'));
        }

        $textAlignment = is_string($raw['textAlignment'] ?? null) ? $raw['textAlignment'] : '';
        if ($textAlignment === '') {
            throw new Exception(fsp__('Text alignment must be provided'));
        }
        if (!in_array($textAlignment, TextWatermarkOptions::ALIGNMENTS, true)) {
            throw new Exception(fsp__('Text alignment must be left, center or right'));
        }

        if (!isset($raw['textOpacity'])) {
            throw new Exception(fsp__('Text opacity must be provided'));
        }
        $textOpacity = (int) $raw['textOpacity'];
        if ($textOpacity < 0 || $textOpacity > 100) {
            throw new Exception(fsp__('Text opacity must be between 0 and 100'));
        }

        if (!isset($raw['bgOpacity'])) {
            throw new Exception(fsp__('Background opacity must be provided'));
        }
        $bgOpacity = (int) $raw['bgOpacity'];
        if ($bgOpacity < 0 || $bgOpacity > 100) {
            throw new Exception(fsp__('Background opacity must be between 0 and 100'));
        }

        $fontSize = (int) ($raw['fontSize'] ?? 0);
        if ($fontSize !== 0 && ($fontSize < 1 || $fontSize > 200)) {
            throw new Exception(fsp__('Font size must be between 1 and 200'));
        }

        $options = new TextWatermarkOptions();
        $options->position = $position;
        $options->vertical = $vertical;
        $options->horizontal = $horizontal;
        $options->textValue = $textValue;
        $options->textAlignment = $textAlignment;
        $options->textOpacity = $textOpacity;
        $options->bgOpacity = $bgOpacity;
        $options->fontSize = $fontSize;
        $options->fontFamily = is_string($raw['fontFamily'] ?? null) ? $raw['fontFamily'] : '';
        $options->textColor = is_string($raw['textColor'] ?? null) ? $raw['textColor'] : '';
        $options->bgColor = is_string($raw['bgColor'] ?? null) ? $raw['bgColor'] : '';
        $options->textWidth = (int) ($raw['textWidth'] ?? 0);

        return $options;
    }

    /**
     * @return string[]
     */
    private function toStringArray($value): array
    {
        if (!is_array($value)) {
            return [];
        }

        $out = [];
        foreach ($value as $item) {
            if (is_string($item) && $item !== '') {
                $out[] = $item;
            }
        }

        return $out;
    }
}
