<?php

declare(strict_types=1);

namespace App\Application\Search;

use App\Application\Search\ValueObjects\FieldBoosts;
use App\Application\Search\ValueObjects\FieldSelection;
use App\Application\Search\ValueObjects\HighlightFields;
use App\Application\Search\ValueObjects\SearchFilters;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;
use App\Framework\Http\Request;

/**
 * Represents a search request with validation
 */
final readonly class SearchRequest
{
    public function __construct(
        public string $query,
        public SearchFilters $filters,
        public FieldBoosts $boosts,
        public FieldSelection $fields,
        public HighlightFields $highlight,
        public int $limit = 20,
        public int $offset = 0,
        public ?string $sortBy = null,
        public string $sortDirection = 'asc',
        public bool $sortByRelevance = true,
        public bool $enableHighlighting = true,
        public bool $enableFuzzy = false,
        public float $minScore = 0.0
    ) {
    }

    public static function fromHttpRequest(Request $request): self
    {
        $query = $request->query;

        // Parse search query
        $searchQuery = $query->get('q', '*') ?? '*';

        // Parse filters
        $filters = [];
        if ($query->has('filters')) {
            $filtersParam = $query->get('filters');
            if (is_string($filtersParam)) {
                $filters = json_decode($filtersParam, true) ?? [];
            } elseif (is_array($filtersParam)) {
                $filters = $filtersParam;
            }
        }

        // Parse individual filter parameters (filter[field][type]=value)
        foreach ($query->toArray() as $key => $value) {
            if (preg_match('/^filter\[([^]]+)](?:\[([^]]+)])?$/', $key, $matches)) {
                $field = $matches[1];
                $type = $matches[2] ?? 'equals';

                if (! isset($filters[$field])) {
                    $filters[$field] = [];
                }

                $filters[$field]['type'] = $type;
                $filters[$field]['value'] = $value;
            }
        }

        // Parse boosts
        $boosts = [];
        if ($query->has('boosts')) {
            $boostsParam = $query->get('boosts');
            if (is_string($boostsParam)) {
                $boosts = json_decode($boostsParam, true) ?? [];
            } elseif (is_array($boostsParam)) {
                $boosts = $boostsParam;
            }
        }

        // Parse fields restriction
        $fields = [];
        if ($query->has('fields')) {
            $fieldsParam = $query->get('fields');
            if (is_string($fieldsParam)) {
                $fields = array_map('trim', explode(',', $fieldsParam));
            } elseif (is_array($fieldsParam)) {
                $fields = $fieldsParam;
            }
        }

        // Parse highlight fields
        $highlight = [];
        if ($query->has('highlight')) {
            $highlightParam = $query->get('highlight');
            if (is_string($highlightParam)) {
                $highlight = array_map('trim', explode(',', $highlightParam));
            } elseif (is_array($highlightParam)) {
                $highlight = $highlightParam;
            }
        } elseif ($query->getBool('enable_highlighting', true)) {
            // Default highlighting for text fields
            $highlight = ['title', 'content', 'description'];
        }

        // Parse pagination
        $limit = min(100, max(1, $query->getInt('limit', 20)));
        $offset = max(0, $query->getInt('offset', 0));

        // Alternative pagination via page parameter
        if ($query->has('page')) {
            $page = max(1, $query->getInt('page', 1));
            $perPage = min(100, max(1, $query->getInt('per_page', 20)));
            $offset = ($page - 1) * $perPage;
            $limit = $perPage;
        }

        // Parse sorting
        $sortBy = $query->get('sort_by');
        $sortDirection = strtolower($query->get('sort_direction', 'asc') ?? 'asc');
        $sortByRelevance = $query->getBool('sort_by_relevance', ! $sortBy);

        // Validate sort direction
        if (! in_array($sortDirection, ['asc', 'desc'])) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                'Invalid sort direction. Must be "asc" or "desc"'
            );
        }

        // Parse advanced options
        $enableHighlighting = $query->getBool('enable_highlighting', true);
        $enableFuzzy = $query->getBool('enable_fuzzy', false);
        $minScore = max(0.0, $query->getFloat('min_score', 0.0));

        return new self(
            query: $searchQuery,
            filters: SearchFilters::fromArray($filters),
            boosts: FieldBoosts::fromArray($boosts),
            fields: FieldSelection::fromArray($fields),
            highlight: empty($highlight) && $enableHighlighting ? HighlightFields::default() : HighlightFields::fromArray($highlight),
            limit: $limit,
            offset: $offset,
            sortBy: $sortBy,
            sortDirection: $sortDirection,
            sortByRelevance: $sortByRelevance,
            enableHighlighting: $enableHighlighting,
            enableFuzzy: $enableFuzzy,
            minScore: $minScore
        );
    }

    public function validate(): void
    {
        if (empty(trim($this->query))) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                'Search query cannot be empty'
            );
        }

        if ($this->limit < 1 || $this->limit > 100) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                'Limit must be between 1 and 100'
            );
        }

        if ($this->offset < 0) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                'Offset cannot be negative'
            );
        }

        if ($this->minScore < 0) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                'Minimum score cannot be negative'
            );
        }

        // Value objects handle their own validation
    }

    /**
     * @return array<string, mixed>
     */
    public function toArray(): array
    {
        return [
            'query' => $this->query,
            'filters' => $this->filters->toArray(),
            'boosts' => $this->boosts->toArray(),
            'fields' => $this->fields->toArray(),
            'highlight' => $this->highlight->toArray(),
            'limit' => $this->limit,
            'offset' => $this->offset,
            'sort_by' => $this->sortBy,
            'sort_direction' => $this->sortDirection,
            'sort_by_relevance' => $this->sortByRelevance,
            'enable_highlighting' => $this->enableHighlighting,
            'enable_fuzzy' => $this->enableFuzzy,
            'min_score' => $this->minScore,
        ];
    }
}
