<?php

namespace App\Services;

use App\Enums\DeadlineExpiryEnum;
use App\Enums\FeatureEnum;
use App\Enums\PublicationDateEnum;
use App\Enums\WatchlistTypeEnum;
use App\Exceptions\ApiHttpException;
use App\Models\Backoffice\MediaItem;
use App\Models\City;
use App\Models\User;
use App\Models\Competition;
use App\Models\State;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Locale;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class CompetitionsService
{
    /**
     * Get competitions
     *
     * @param User $user
     * @param array $params
     * @return Paginator
     */
    public function getCompetitions(array $params, ?User $user)
    {
        $itemsPerPage = $params['itemsPerPage'] ?? 5;
        $page = $params['page'] ?? 1;
        $filters = $params['filters'] ?? [];
        $orderDir = $params['orderDir'] ?? 'desc';
        $orderBy = $params['orderBy'] ?? 'id';

        $query = Competition::full()
            ->select([
                'competitions.id', 'wa_id', 'slug', 'uri', 'competition_languages.title', 'location_country_id', 'location_city', 'publication_date',
                'submission_until', 'promoter', 'official_number', 'is_public', 'competition_languages.competition_id',
                'competition_languages.language_id', 'translation_id', 'updated_competition_id',
            ])
            ->with(['languages', 'locationCountry' => function ($query) {
                    $query->full();
                },'procedureTypes' => function ($query) {
                $query->full();
                }, 'nuts3Region', 'cpvs' => function ($query) {
                    $query->full()->with('departments');
                }])
            ->where('status', 'published');

        // Eager load watchlist relation with filter for current user or null if no user (to prevent loading all watchlists)
        $query->with(['watchlists' => function ($query) use ($user) {
            $query->where('user_id', $user ? $user->id : null)
                ->where('type', WatchlistTypeEnum::COMPETITIONS->value);
        }]);

        $this->_applyCompetitionPreFilters($query, $filters);

        if (array_key_exists('publicationDate', $filters)) {
            switch ($filters['publicationDate']['type']) {
                case PublicationDateEnum::SINCE->value:
                    $query->where('publication_date', '>=', $filters['publicationDate']['value']);
                    break;
                case PublicationDateEnum::LAST_1->value:
                    $query->where('publication_date', '>=', Carbon::now()->subDays(1)->startOfDay());
                    break;
                case PublicationDateEnum::LAST_7->value:
                    $query->where('publication_date', '>=', Carbon::now()->subDays(7)->startOfDay());
                    break;
                case PublicationDateEnum::LAST_14->value:
                    $query->where('publication_date', '>=', Carbon::now()->subDays(14)->startOfDay());
                    break;
            }
        }

        if (array_key_exists('deadlineExpiry', $filters)) {
            switch ($filters['deadlineExpiry']['type']) {
                case DeadlineExpiryEnum::ALL_VALID->value:
                    $query->where('submission_until', '>=', Carbon::now());
                    break;
                case DeadlineExpiryEnum::ALL_EXPIRED->value:
                    $query->where('submission_until', '<', Carbon::now());
                    break;
                case DeadlineExpiryEnum::EARLIEST_IN->value:
                    $query->where('submission_until', '>=', Carbon::now()->addDays(intval($filters['deadlineExpiry']['value'])));
                    break;
                case DeadlineExpiryEnum::EARLIEST_ON->value:
                    $query->where('submission_until', '>=', $filters['deadlineExpiry']['value']);
                    break;
            }
        }

        switch ($orderBy) {
            case 'id':
                $query->orderBy('competitions.id', $orderDir);
                break;
            case 'publicationDate':
                $query->orderBy('publication_date', $orderDir)
                      ->orderBy('competitions.id', $orderDir);
            case 'relevance':
                if (!empty($filters['search'])) {
                    $query->orderByRaw("MATCH(website_objects_search.search_content)
                        AGAINST (? IN NATURAL LANGUAGE MODE) DESC", [$filters['search']]);
                }
                break;
        }

        $competitions = $query->paginate($itemsPerPage, ['*'], 'page', $page);
        return $competitions;
    }

    /**
     * Get competition by slug
     *
     * @param string $slug
     * @return Competition
     */
    public function getCompetitionBySlug(string $slug, ?User $user)
    {
        $query = Competition::full()->where('slug', $slug)->where('status', 'published');

        if ($user) {
            $query->with(['watchlists' => function ($query) use ($user) {
                $query->where('user_id', $user->id)
                    ->where('type', WatchlistTypeEnum::COMPETITIONS->value);
            }]);
        }

        $competition = $query->first();

        if (!$competition) {
            return null;
        }

        if (!$competition->is_public) {
            // User not logged or doesn't have any competition permissions
            if (!$user || (!$user->hasFeature(FeatureEnum::FULL_COMPETITIONS) && !$user->hasFeature(FeatureEnum::STUDENT_COMPETITIONS))) {
                throw new ApiHttpException($user ? 403 : 401, 'access_denied');
            }

            // User only has student permission
            if (!$user->hasFeature(FeatureEnum::FULL_COMPETITIONS) && !$competition->procedureTypes->contains(fn ($procedureType) => $procedureType->is_open_to_students)) {
                throw new ApiHttpException(403, 'access_denied');
            }
        }

        return $competition;
    }

    public function getFiltersCount(array $params)
    {
        $filters = $params['filters'] ?? [];

        $query = Competition::full()->where('status', 'published');

        $query = $this->_applyCompetitionPreFilters($query, $filters);

        $counts = $query->selectRaw("
                MAX(competitions.id) as id,
                COUNT(*) AS count_all,
                COUNT(CASE WHEN publication_date >= DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 1 END) AS count_last_7,
                COUNT(CASE WHEN publication_date >= DATE_SUB(NOW(), INTERVAL 14 DAY) THEN 1 END) AS count_last_14,

                COUNT(CASE WHEN submission_until >= NOW() THEN 1 END) AS count_valid,
                COUNT(CASE WHEN submission_until < NOW() THEN 1 END) AS count_expired,
                COUNT(CASE WHEN submission_until >= DATE_ADD(NOW(), INTERVAL ? DAY) THEN 1 END) AS count_earliest_in
            ", [
                intval($filters['deadlineExpiry']['value'] ?? 0),
            ])->groupByRaw("NULL")
            ->first();

        return [
            'publicationDate' => [
                'all' => $counts ? $counts->count_all : 0,
                'last7' => $counts ? $counts->count_last_7 : 0,
                'last14' => $counts ? $counts->count_last_14 : 0,
            ],
            'submissionUntil' => [
                'valid' => $counts ?  $counts->count_valid : 0,
                'expired' => $counts ?  $counts->count_expired : 0,
                'EarliestIn' => $counts ?  $counts->count_earliest_in : 0,
            ]
        ];
    }

    private function _applyCompetitionPreFilters(Builder $query, array $filters)
    {
        $lang = LaravelLocalization::getCurrentLocale();

        if (!empty($filters['typeOfService'])) {
            $query->whereHas('cpvs', function ($query) use ($filters) {
                $query->whereHas('departments', function ($query) use ($filters) {
                    $query->whereIn('id', $filters['typeOfService']);
                });
            });
        }

        if (!empty($filters['typeOfAnnouncement'])) {
            $query->whereHas('procedureTypes', function ($query) use ($filters) {
                $query->whereIn('procedure_types.id', $filters['typeOfAnnouncement']);
            });
        }

        if (!empty($filters['locations'])) {
            $cityIds = array_column($filters['locations'], 'id');
            $stateIds = array_column($filters['locations'], 'stateId');

            $cities = City::whereIn('id', $cityIds)->get()->keyBy('id');
            $states = State::whereIn('id', $stateIds)->get()->keyBy('id');

            $locationInfo = array_filter(array_map(function($location) use ($cities) {
                if (isset($location['id']) && $cities->has($location['id'])) {
                    return [
                        'location' => $cities[$location['id']]->location ?? null,
                        'radius'   => $location['radius']
                    ];
                }
            }, $filters['locations']));

            $query->where(function($query) use ($states, $locationInfo) {
                if (!empty($locationInfo)) {
                    $query->orWhereHas('nuts3Region', function($query) use ($locationInfo) {
                        $query->where(function ($query) use ($locationInfo) {
                            foreach ($locationInfo as $locationData) {
                                $lat = $locationData['location']->latitude;
                                $lon = $locationData['location']->longitude;
                                $radius = $locationData['radius']; // KM
                                $query->orWhereRaw("ST_Distance_Sphere(location, POINT(?, ?)) <= ?",
                                    [$lon, $lat, $radius * 1000]
                                );
                            }
                        });
                    });
                }

                if ($states->count()) {
                    $stateNutsCodes = $states->pluck('nuts_code');
                    $query->orWhereHas('nuts3Region', function($query) use ($stateNutsCodes) {
                        $query->where(function ($query) use ($stateNutsCodes) {
                            foreach ($stateNutsCodes as $nutsCode) {
                                if (!$nutsCode) {
                                    // If a state has no NUTS code assigned, fakes a non-match
                                    $query->orWhere('code', 'like', 'FAKE_NUTS_CODE');
                                    continue;
                                }
                                $query->orWhere('code', 'like', "{$nutsCode}" . '%');
                            }
                        });
                    });
                }
            });
        }

        if (!empty($filters['countries'])) {
            $query->whereIn('location_country_id', $filters['countries']);
        }

        if (!empty($filters['search'])) {
            $query->join("website_objects_search", "website_objects.website_object_id", "=", "website_objects_search.website_object_id")
                ->whereRaw("website_objects.website_object_id = website_objects_search.website_object_id")
                ->where(function ($query) use ($filters) {
                    $searchTerm = $filters['search'];
                    $query->whereRaw("MATCH(search_content)
                        AGAINST (? IN NATURAL LANGUAGE MODE)", array($searchTerm))
                        ->orWhere('competition_languages.title', 'like', '%' . $searchTerm . '%');
                })
                ->where("website_objects_search.language_id", $lang);
        }

        if (!empty($filters['cpvs'])) {
            $query->whereHas('cpvs', function ($query) use ($filters) {
                $query->whereIn('id', $filters['cpvs']);
            });
        }
        return $query;
    }

    public function getCompetitionDocument(int $mediaItemId, ?User $user)
    {
        $mediaItem = MediaItem::find($mediaItemId);

        if (!$mediaItem) {
            throw new ApiHttpException(404, 'document_not_found');
        }

        // Check if the document belongs to a published competition
        $competition = Competition::full()
            ->whereHas('documents', function ($query) use ($mediaItemId) {
                $query->where('media_items.id', $mediaItemId);
            })
            ->where('status', 'published')
            ->first();

        if (!$competition) {
            throw new ApiHttpException(404, 'document_not_found');
        }

        // Apply same access control as viewing the competition
        if (!$competition->is_public) {
            if (!$user || (!$user->hasFeature(FeatureEnum::FULL_COMPETITIONS) && !$user->hasFeature(FeatureEnum::STUDENT_COMPETITIONS))) {
                throw new ApiHttpException($user ? 403 : 401, 'access_denied');
            }

            if (!$user->hasFeature(FeatureEnum::FULL_COMPETITIONS) && !$competition->procedureTypes->contains(fn ($procedureType) => $procedureType->is_open_to_students)) {
                throw new ApiHttpException(403, 'access_denied');
            }
        }

        return $mediaItem;
    }
}
