<?php

namespace App\Http\Controllers;

use App\Enums\WorkoutSource;
use App\Http\Requests\StravaCallbackRequest;
use App\Http\Requests\StravaSyncRequest;
use App\Models\StravaConnection;
use App\Models\WorkoutSessions;
use App\Services\MobileDeepLink;
use App\Services\StravaClient;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;

class StravaController extends Controller
{
    public function authorizationUrl(Request $request, StravaClient $strava): JsonResponse
    {
        $state = Str::random(40);

        Cache::put($this->stateCacheKey($state), [
            'user_id' => $request->user()->getKey(),
        ], now()->addMinutes(10));

        return response()->json([
            'authorizationUrl' => $strava->authorizationUrl($state),
            'scope' => config('services.strava.scope', 'read,activity:read'),
        ]);
    }

    public function redirectToMobileApp(Request $request): RedirectResponse
    {
        $params = collect($request->only(['code', 'error', 'scope', 'state']))
            ->filter(fn ($value): bool => $value !== null && $value !== '')
            ->all();

        return redirect()->away(MobileDeepLink::to('strava/callback', $params));
    }

    public function callback(StravaCallbackRequest $request, StravaClient $strava): JsonResponse
    {
        $data = $request->validated();
        $state = Cache::pull($this->stateCacheKey($data['state']));

        if (! is_array($state) || $state['user_id'] !== $request->user()->getKey()) {
            return response()->json([
                'message' => __('api.strava.state_expired'),
            ], 422);
        }

        if (isset($data['error'])) {
            return response()->json([
                'message' => __('api.strava.refused'),
            ], 422);
        }

        $payload = $strava->exchangeCode($data['code']);
        $scopes = $this->normalizeScopes($payload['scope'] ?? $data['scope'] ?? '');

        $connection = $request->user()->stravaConnection()->updateOrCreate([], [
            'athlete_id' => data_get($payload, 'athlete.id'),
            'access_token' => $payload['access_token'],
            'refresh_token' => $payload['refresh_token'],
            'expires_at' => Carbon::createFromTimestamp((int) $payload['expires_at']),
            'scopes' => $scopes,
            'athlete' => $payload['athlete'] ?? null,
        ]);

        return response()->json([
            'data' => [
                'connected' => true,
                'athleteId' => $connection->athlete_id,
                'scopes' => $connection->scopes,
                'expiresAt' => $connection->expires_at,
            ],
        ]);
    }

    public function status(Request $request): JsonResponse
    {
        $connection = $request->user()->stravaConnection;
        $scopes = $connection?->scopes ?? [];

        return response()->json([
            'data' => [
                'connected' => $connection !== null,
                'athleteId' => $connection?->athlete_id,
                'canReadActivities' => $connection !== null && $this->canReadActivities($scopes),
                'requestedScopes' => $this->normalizeScopes(config('services.strava.scope', 'read,activity:read')),
                'scopes' => $scopes,
                'expiresAt' => $connection?->expires_at,
            ],
        ]);
    }

    public function disconnect(Request $request, StravaClient $strava): Response|JsonResponse
    {
        $connection = $request->user()->stravaConnection;

        if (! $connection) {
            return response()->noContent();
        }

        try {
            $strava->deauthorize($connection);
        } catch (RequestException $exception) {
            if ($exception->response->status() !== 401) {
                report($exception);

                return response()->json([
                    'message' => __('api.strava.disconnect_failed'),
                ], 502);
            }
        } catch (ConnectionException $exception) {
            report($exception);

            return response()->json([
                'message' => __('api.strava.disconnect_failed'),
            ], 502);
        }

        $connection->delete();

        return response()->noContent();
    }

    public function sync(StravaSyncRequest $request, StravaClient $strava): JsonResponse
    {
        $connection = $request->user()->stravaConnection;

        if (! $connection) {
            return response()->json([
                'message' => __('api.strava.missing_connection'),
            ], 422);
        }

        if (! $this->canReadActivities($connection->scopes ?? [])) {
            return response()->json([
                'message' => __('api.strava.missing_activity_scope'),
            ], 422);
        }

        $activities = collect($strava->activities(
            $connection,
            $request->integer('page', 1),
            $request->integer('per_page', 30),
        ))
            ->map(fn (array $activity): array => $this->activityForStorage($strava, $connection, $activity))
            ->all();

        $importedCount = collect($activities)
            ->map(fn (array $activity): WorkoutSessions => $this->storeActivity($request, $activity))
            ->count();

        return response()->json([
            'data' => [
                'importedCount' => $importedCount,
                'fetchedCount' => count($activities),
            ],
        ]);
    }

    private function storeActivity(Request $request, array $activity): WorkoutSessions
    {
        return $request->user()->workouts()->updateOrCreate([
            'source' => WorkoutSource::STRAVA,
            'external_id' => (string) $activity['id'],
        ], [
            'type' => $activity['sport_type'] ?? $activity['type'] ?? 'workout',
            'title' => $activity['name'] ?? 'Strava workout',
            'duration_seconds' => max(1, (int) ($activity['moving_time'] ?? $activity['elapsed_time'] ?? 0)),
            'calories_burned' => (int) round((float) ($activity['calories'] ?? 0)),
            'distance_meters' => (int) round((float) ($activity['distance'] ?? 0)),
            'started_at' => $activity['start_date'] ?? null,
        ]);
    }

    private function activityForStorage(StravaClient $strava, StravaConnection $connection, array $activity): array
    {
        if (array_key_exists('calories', $activity) || empty($activity['id'])) {
            return $activity;
        }

        return array_replace($activity, $strava->activity($connection, $activity['id']));
    }

    private function canReadActivities(array $scopes): bool
    {
        return in_array('activity:read', $scopes, true)
            || in_array('activity:read_all', $scopes, true);
    }

    private function normalizeScopes(string $scope): array
    {
        return Str::of($scope)
            ->replace(',', ' ')
            ->explode(' ')
            ->filter()
            ->values()
            ->all();
    }

    private function stateCacheKey(string $state): string
    {
        return "strava:oauth-state:{$state}";
    }
}
