<?php

namespace App\Services;

use App\Enums\FeatureEnum;
use App\Enums\WatchlistTypeEnum;
use App\Exceptions\ApiHttpException;
use App\Models\CompanyProfile;
use App\Models\Competition;
use App\Models\JobOffer;
use App\Models\Result;
use App\Models\TrackedResult;
use App\Models\User;
use App\Models\Watchlist;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;

class WatchlistsService
{
    public function createDefaultWatchlists(User $user): void
    {
        foreach (WatchlistTypeEnum::cases() as $type) {
            $this->createWatchlist(
                $user,
                ['name' => WatchlistTypeEnum::labels()[$type->value] ?? 'Favorites'],
                true,
                $type->value
            );
        }
    }

    /**
     * Create a new watchlist
     *
     * @param User $user
     * @param Array $data
     * @param bool $isDefault
     * @param string|null $type
     * @return Watchlist
     */
    public function createWatchlist(User $user, Array $data, $isDefault = false, ?string $type = null): Watchlist
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS) && !$isDefault) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $resolvedType = $this->resolveType($type ?? ($data['type'] ?? WatchlistTypeEnum::COMPETITIONS->value));

        if ($isDefault) {
            $existingDefault = $user->watchlists()
                ->where('is_default', true)
                ->where('type', $resolvedType)
                ->first();

            if ($existingDefault) {
                return $existingDefault;
            }
        }

        $watchlist = new Watchlist();

        $watchlist->name = $data['name'] ?? (WatchlistTypeEnum::labels()[$resolvedType] ?? 'Favorites');
        $watchlist->is_default = $isDefault;
        $watchlist->type = $resolvedType;
        $watchlist->user()->associate($user);
        $watchlist->save();

        return $watchlist;
    }

    /**
     * Update a watchlist
     *
     * @param User $user
     * @param int $watchlistId
     * @param Array $data
     * @return Watchlist
     */
    public function updateWatchlist(User $user, int $watchlistId, $data): Watchlist
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $watchlist = $this->getUserWatchlist($user, $watchlistId);

        if ($watchlist->is_default) {
            throw new ApiHttpException(403, trans('errors.watchlist_default_not_editable'));
        }

        $watchlist->name = $data['name'];
        $watchlist->save();

        return $watchlist;
    }

    /**
     * Delete a watchlist
     *
     * @param User $user
     * @param int $watchlistId
     * @return bool
     */
    public function deleteWatchlist(User $user, int $watchlistId): bool
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $watchlist = $this->getUserWatchlist($user, $watchlistId);

        if ($watchlist->is_default) {
            throw new ApiHttpException(403, trans('errors.watchlist_default_not_editable'));
        }

        $watchlist->delete();

        return true;
    }

    /**
     * Add a competition to a watchlist
     *
     * @param User $user
     * @param int $watchlistId
     * @param array $competitionIds
     * @return bool
     */
    public function addCompetitionsToWatchlist(User $user, int $watchlistId, array $competitionIds): bool
    {
        $watchlist = $this->getUserWatchlist($user, $watchlistId, WatchlistTypeEnum::COMPETITIONS->value);

        return $this->addItemsToWatchlist($user, $watchlist->id, $competitionIds);
    }

    public function addItemsToWatchlist(User $user, int $watchlistId, array $itemIds): bool
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $watchlist = $this->getUserWatchlist($user, $watchlistId);

        $itemIds = array_values(array_unique($itemIds));
        $this->validateItemIds($watchlist->type, $itemIds);

        $relationName = $watchlist->getItemsRelationName();
        $relation = $watchlist->{$relationName}();

        $ids = [];
        $count = $relation->count();
        foreach ($itemIds as $itemId) {
            $ids[$itemId] = ['position' => $count + 1];
            $count++;
        }

        $relation->syncWithoutDetaching($ids);

        return true;
    }

    /**
     * Remove a competition from a watchlist
     *
     * @param User $user
     * @param int $watchlistId
     * @param array $competitionIds
     * @return bool
     */
    public function removeCompetitionsFromWatchlist(User $user, int $watchlistId, array $competitionIds): bool
    {
        $watchlist = $this->getUserWatchlist($user, $watchlistId, WatchlistTypeEnum::COMPETITIONS->value);

        return $this->removeItemsFromWatchlist($user, $watchlist->id, $competitionIds);
    }

    public function removeItemsFromWatchlist(User $user, int $watchlistId, array $itemIds): bool
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $watchlist = $this->getUserWatchlist($user, $watchlistId);

        $relationName = $watchlist->getItemsRelationName();
        $relation = $watchlist->{$relationName}();

        $relation->detach($itemIds);

        return true;
    }

    /**
     * Get all watchlists for a user
     *
     * @param User $user
     * @param string|null $type
     * @return Collection
     */
    public function getWatchlists(User $user, ?string $type = null): Collection
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $this->createDefaultWatchlists($user);
        $resolvedType = $type ? $this->resolveType($type) : null;

        return $user->watchlists()
            ->when($resolvedType, function ($query) use ($resolvedType) {
                $query->where('type', $resolvedType);
            })
            ->withCount(['competitions', 'results', 'trackedResults', 'companyProfiles', 'jobs'])
            ->get();
    }

    /**
     * Get a watchlist by id
     *
     * @param User $user
     * @param int $watchlistId
     * @param string|null $type
     * @return Watchlist
     */
    public function getWatchlist(User $user, int $watchlistId, ?string $type = null): Watchlist
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $this->createDefaultWatchlists($user);

        return $this->getUserWatchlist($user, $watchlistId, $type);
    }

    /**
     * Paginate through competitions in a watchlist
     *
     * @param User $user
     * @param int $watchlistId
     * @return Collection
     */
    public function getWatchlistCompetitions(User $user, int $watchlistId, Array $params): LengthAwarePaginator
    {
        $watchlist = $this->getUserWatchlist($user, $watchlistId, WatchlistTypeEnum::COMPETITIONS->value);

        return $this->getWatchlistItems($user, $watchlist->id, $params);
    }

    public function getWatchlistItems(User $user, int $watchlistId, Array $params): LengthAwarePaginator
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $watchlist = $this->getUserWatchlist($user, $watchlistId);

        switch ($watchlist->type) {
            case WatchlistTypeEnum::COMPETITIONS->value:
                $competitionsQuery = $watchlist->competitions()->full()
                    ->with(['locationCountry', 'cpvs' => function ($query) {
                        $query->full()->with('departments');
                    }])->where('status', 'published');

                /*
                if (isset($params['expiredOnly']) && $params['expiredOnly'] == true) {
                    $competitionsQuery->where('submission_until', '<', now());
                } else {
                    $competitionsQuery->where('submission_until', '>', now());
                }
                */

                return $competitionsQuery->orderBy('publication_date', $params['orderDir'])
                    ->orderBy('id', $params['orderDir'])
                    ->paginate($params['itemsPerPage'], ['*'], 'page', $params['page']);

            case WatchlistTypeEnum::RESULTS->value:
                return $watchlist->results()->full()
                    ->with(['competition.locationCountry', 'image'])
                    ->where('status', 'published')
                    ->orderBy('updated_date', $params['orderDir'])
                    ->orderBy('id', $params['orderDir'])
                    ->paginate($params['itemsPerPage'], ['*'], 'page', $params['page']);

            case WatchlistTypeEnum::TRACKED_RESULTS->value:
                return $watchlist->trackedResults()->full()
                    ->with(['competition.locationCountry', 'image'])
                    ->where('status', 'published')
                    ->orderBy('updated_date', $params['orderDir'])
                    ->orderBy('id', $params['orderDir'])
                    ->paginate($params['itemsPerPage'], ['*'], 'page', $params['page']);

            case WatchlistTypeEnum::COMPANY_PROFILES->value:
                return $watchlist->companyProfiles()
                    ->orderBy('company_profiles.id', $params['orderDir'])
                    ->paginate($params['itemsPerPage'], ['*'], 'page', $params['page']);

            case WatchlistTypeEnum::JOBS->value:
                return $watchlist->jobs()
                    ->orderBy('job_offers.id', $params['orderDir'])
                    ->paginate($params['itemsPerPage'], ['*'], 'page', $params['page']);

            default:
                throw new ApiHttpException(422, trans('errors.watchlist_not_found'));
        }
    }

    public function getWatchlistCompetitionsCount(User $user, int $watchlistId): array
    {
        $watchlist = $this->getUserWatchlist($user, $watchlistId, WatchlistTypeEnum::COMPETITIONS->value);

        return $this->getWatchlistItemsCount($user, $watchlist->id);
    }

    public function getWatchlistItemsCount(User $user, int $watchlistId): array
    {
        if (!$user->hasFeature(FeatureEnum::WATCHLISTS)) {
            throw new ApiHttpException(403, trans('errors.watchlist_feature_not_enabled'));
        }

        $watchlist = $this->getUserWatchlist($user, $watchlistId);

        if ($watchlist->type === WatchlistTypeEnum::COMPETITIONS->value) {
            $counts = $watchlist->competitions()->full()
                ->toBase()
                ->selectRaw("
                    COUNT(CASE WHEN submission_until >= NOW() THEN 1 END) AS count_valid,
                    COUNT(CASE WHEN submission_until < NOW() THEN 1 END) AS count_expired
                ")
                ->groupByRaw('NULL')
                ->first();

            return [
                'valid' => $counts ? $counts->count_valid : 0,
                'expired' => $counts ? $counts->count_expired : 0,
            ];
        }

        $relationName = $watchlist->getItemsRelationName();

        return [
            'total' => $watchlist->{$relationName}()->count(),
        ];
    }

    private function resolveType(string $type): string
    {
        $resolved = WatchlistTypeEnum::tryFrom($type);

        if (!$resolved) {
            throw new ApiHttpException(422, 'Invalid watchlist type');
        }

        return $resolved->value;
    }

    private function getUserWatchlist(User $user, int $watchlistId, ?string $type = null): Watchlist
    {
        $resolvedType = $type ? $this->resolveType($type) : null;

        $watchlist = $user->watchlists()
            ->where('id', $watchlistId)
            ->when($resolvedType, function ($query) use ($resolvedType) {
                $query->where('type', $resolvedType);
            })
            ->first();

        if (!$watchlist) {
            throw new ApiHttpException(404, trans('errors.watchlist_not_found'));
        }

        return $watchlist;
    }

    private function validateItemIds(string $type, array $itemIds): void
    {
        if (empty($itemIds)) {
            return;
        }

        $modelClass = match ($type) {
            WatchlistTypeEnum::COMPETITIONS->value => Competition::class,
            WatchlistTypeEnum::RESULTS->value => Result::class,
            WatchlistTypeEnum::TRACKED_RESULTS->value => TrackedResult::class,
            WatchlistTypeEnum::COMPANY_PROFILES->value => CompanyProfile::class,
            WatchlistTypeEnum::JOBS->value => JobOffer::class,
            default => null,
        };

        if (!$modelClass) {
            throw new ApiHttpException(422, 'Invalid watchlist type');
        }

        $count = $modelClass::query()->whereIn('id', $itemIds)->count();

        if ($count !== count($itemIds)) {
            throw new ApiHttpException(422, 'Some item ids are invalid');
        }
    }
}

