<?php

namespace App\Master\Http\Controllers;

use App\Contracts\GoLiveChecklist;
use App\Contracts\GoLiveStatus;
use App\Facades\Feature;
use App\Http\Controllers\Controller;
use App\Http\Resources\SiteGoLiveResource;
use App\IntegrationRepository;
use App\Jobs\RefreshFilterIndexJob;
use App\Master\Http\Requests\InviteUserToSiteRequest;
use App\Master\Http\Requests\RemoveUserFromSiteRequest;
use App\Master\Http\Requests\SiteRequest;
use App\Master\Http\Requests\SiteStoreRequest;
use App\Master\Http\Requests\UpdateUserRoleOnSiteRequest;
use App\Master\Http\Resources\SiteListResource;
use App\Master\SiteManager;
use App\Services\CloudflareService;
use App\Tier;
use Exception;
use Illuminate\Contracts\Queue\Factory;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\App;
use Illuminate\Validation\ValidationException;
use Mtc\MercuryDataModels\Billable;
use Mtc\MercuryDataModels\Role;
use Mtc\MercuryDataModels\Tenant;
use Mtc\MercuryDataModels\TenantType;

class SiteController extends Controller
{
    public function __construct(private SiteManager $manager)
    {
        $this->middleware([
            'permission:manage-sites'
        ]);
    }

    /**
     * List available user roles on site
     *
     * @param SiteRequest $request
     * @return mixed
     */
    public function roles(SiteRequest $request)
    {
        return Role::query()
            ->when(
                $request->filled('tenant'),
                fn($query) => $query->where(fn($query) => $query->where('tenant', $request->input('tenant'))
                    ->orWhereNull('tenant'))
            )
            ->notMtc()
            ->get();
    }

    public function queueLengths(Factory $manager)
    {
        return collect(config('app.active-queues'))->map(fn($queueName) => [
            'name' => __('labels.queues.' . $queueName),
            'value' => $manager->connection(config('queue.default'))->size($queueName),
        ]);
    }

    /**
     * Display a list of sites in system.
     *
     * @return SiteListResource
     */
    public function index(SiteRequest $request): SiteListResource
    {
        $exclude_suspended = filter_var($request->input('exclude_suspended'), FILTER_VALIDATE_BOOLEAN);
        $filter = $request->input('filters');
        $favouriteSiteIds = $request->user()->favouriteSites()->pluck('tenant_id')->toArray();

        $sites = Tenant::query()
            ->when(
                !empty($favouriteSiteIds),
                function ($query) use ($favouriteSiteIds) {
                    $favourites = implode(',', array_map(fn($id) => "'{$id}'", $favouriteSiteIds));
                    return $query
                        ->selectRaw('*, CASE WHEN id IN (' . $favourites . ') THEN 0 ELSE 1 END as is_favourite_sort')
                        ->orderBy('is_favourite_sort');
                }
            )
            ->orderBy('name')
            ->with([
                'users',
                'domains',
                'features',
            ])
            ->when(
                !empty($request->input('term')),
                fn($query) => $query->where('name', 'like', '%' . trim($request->input('term')) . '%')
            )
            ->when($filter === 'live', fn($query) => $query->whereNotNull('live_at'))
            ->when($filter === 'in_development', fn($query) => $query->whereNull('suspended_at')->whereNull('live_at'))
            ->when($filter === 'suspended', fn($query) => $query->whereNotNull('suspended_at'))
            ->when($exclude_suspended, fn($query) => $query->whereNull('suspended_at'))
            ->paginate($request->input('per_page', 15));

        return new SiteListResource($sites, $favouriteSiteIds);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     * @return SiteListResource
     */
    public function store(SiteStoreRequest $request): SiteListResource
    {
        $this->manager->addSiteRecord($request->input('name'), '', $request->input('tier'));
        return $this->index($request);
    }

    /**
     * Invite user to site
     *
     * @param InviteUserToSiteRequest $request
     * @param Tenant $site
     * @return SiteListResource|string[]
     */
    public function invite(InviteUserToSiteRequest $request, Tenant $site)
    {
        if ($this->manager->addUser($request->input('email'), $site->id, $request->input('role'))) {
            return $this->index($request);
        }
        return [
            'error' => 'Failed to add user',
        ];
    }

    /**
     * Remove user from site
     *
     * @param RemoveUserFromSiteRequest $request
     * @param Tenant $site
     * @return SiteListResource|string[]
     */
    public function removeUser(RemoveUserFromSiteRequest $request, Tenant $site): array|SiteListResource
    {
        if ($this->manager->removeUser($request->input('userId'), $site->id)) {
            return $this->index($request);
        }

        return [
            'error' => 'Failed to remove user from site',
        ];
    }

    /**
     * Update users role on site
     *
     * @param UpdateUserRoleOnSiteRequest $request
     * @param Tenant $site
     * @return SiteListResource|string[]
     */
    public function updateUserRole(UpdateUserRoleOnSiteRequest $request, Tenant $site): array|SiteListResource
    {
        if ($this->manager->changeUserRole($site->id, $request->input('userId'), $request->input('role'))) {
            return $this->index($request);
        }
        return [
            'error' => 'Failed to update user',
        ];
    }

    public function destroy(Tenant $site)
    {
        if (empty($site->suspended_at)) {
            throw ValidationException::withMessages([
                'Site needs to be suspended before deletion',
            ]);
        }

        $site->delete();
        return \response('ok');
    }

    /**
     * Suspend/Deactivate  site
     *
     * @param SiteRequest $request
     * @param Tenant $site
     * @return SiteListResource|string[]
     */
    public function suspendSite(SiteRequest $request, Tenant $site): array|SiteListResource
    {
        if ($this->manager->suspendSite($site, $request->user(), $request->input('message', ''))) {
            return $this->index($request);
        }
        return [
            'error' => 'Failed to suspend site',
        ];
    }

    public function markLive(SiteRequest $request, Tenant $site): Response
    {
        if ($this->manager->markLive($site, $request->user())) {
            return \response('ok');
        }
        return response([
            'error' => 'Failed to mark site live',
        ]);
    }

    /**
     * Unsuspend / reactivate site
     *
     * @param SiteRequest $request
     * @param Tenant $site
     * @return SiteListResource|string[]
     */
    public function unsuspendSite(SiteRequest $request, Tenant $site): array|SiteListResource
    {
        if ($this->manager->unsuspendSite($site)) {
            return $this->index($request);
        }
        return [
            'error' => 'Failed to unsuspend site',
        ];
    }

    public function billing(Tenant $site): array
    {
        tenancy()->initialize($site);

        return [
            'site' => new SiteGoLiveResource($site),
            'billables' => Billable::all()->groupBy('type'),
            'features' => Feature::getAll(),
            'types' => array_map(function ($type) {
                return [
                    'value' => $type->value,
                    'label' => ucfirst($type->value),
                ];
            }, TenantType::cases()),
        ];
    }

    public function goLiveTasks(): SiteGoLiveResource
    {
        return new SiteGoLiveResource(tenant());
    }

    public function triggerGoLiveTask(string $key): SiteGoLiveResource
    {
        collect(config('app.go_live_checklist', []))
            ->map(fn($checklistClass) => App::make($checklistClass))
            ->filter(fn(GoLiveChecklist $task) => $task->key() === $key)
            ->each(fn(GoLiveChecklist $task) => $task->start(true));
        return new SiteGoLiveResource(tenant());
    }

    public function triggerAllGoLive(): SiteGoLiveResource
    {
        collect(config('app.go_live_checklist', []))
            ->map(fn($checklistClass) => App::make($checklistClass))
            ->reject(fn(GoLiveChecklist $task) => $task->status() === GoLiveStatus::COMPLETED)
            ->reject(fn(GoLiveChecklist $task) => $task->requiresManualConfirm())
            ->each(fn(GoLiveChecklist $task) => $task->start());
        return new SiteGoLiveResource(tenant());
    }

    public function resetGoLive(): SiteGoLiveResource
    {
        tenant()->goLiveTasks()->delete();
        return new SiteGoLiveResource(tenant());
    }

    public function updateBilling(Request $request, Tenant $site): Response
    {
        $site->matomo_site_id = $request->input('matomo_site_id');
        $site->update([
            'type' => $request->input('type'),
            'name' => $request->input('name'),
            'tier' => $request->input('tier'),
            'has_cloudflare' => $request->input('has_cloudflare'),
            'cloudflare_data' => $request->input('cloudflare_data'),
            'theme_name' => $request->input('tier') === Tier::ENTERPRISE->value ? $request->input('theme_name') : null,
        ]);

        Feature::setEnabled($site, $request->input('enabled_features', []));
        return response('success');
    }

    public function clearCloudflare(Tenant $site, CloudflareService $service)
    {
        if (!$site->has_cloudflare) {
            throw ValidationException::withMessages(['Site does not have cloudflare enabled']);
        }

        $response = $service->clear($site);
        if ($response->successful() && $response->json('success')) {
            return response('Cache cleared');
        }
        throw ValidationException::withMessages(collect($response->json('errors'))->pluck('message')->toArray());
    }

    /**
     * @throws Exception
     */
    public function triggerAction(Request $request, Tenant $site): Response
    {
        $this->validate($request, ['action' => 'required']);
        tenancy()->initialize($site->id);
        match ($request->input('action')) {
            'refresh-filter' => $this->dispatch(new RefreshFilterIndexJob()),
            default => throw new \Exception('Unrecognized action'),
        };
        return response('success');
    }

    public function integrations(IntegrationRepository $repository): Response
    {
        return response(
            Tenant::query()
                ->whereNull('suspended_at')
                ->get()
                ->map(fn(Tenant $tenant) => $repository->enabledByType($tenant)->merge([
                    'name' => $tenant->name,
                    'id' => $tenant->id,
                    'tier' => $tenant->tier,
                ]))
        );
    }
}
