<?php

namespace App\Http\Controllers;

use App\Models\TikTokConnection;
use App\Models\User;
use App\Services\TikTokService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Throwable;

class TikTokController extends Controller
{
    public function setup(Request $request): View
    {
        return view('tiktok.setup', [
            'connection' => TikTokConnection::query()
                ->where('user_id', $request->user()->getKey())
                ->first(),
        ]);
    }

    public function authorize(Request $request): RedirectResponse
    {
        if (blank(config('services.tiktok.app_id')) || blank(config('services.tiktok.app_secret'))) {
            return back()->withErrors(['connection' => 'TikTok OAuth is not configured. Add TIKTOK_APP_ID and TIKTOK_APP_SECRET.']);
        }

        $state = Crypt::encryptString(json_encode([
            'user_id' => $request->user()->getKey(),
            'nonce' => Str::random(40),
        ], JSON_THROW_ON_ERROR));
        $request->session()->put('tiktok.oauth_state', $state);

        return redirect()->away((string) config('services.tiktok.oauth_url').'?'.http_build_query([
            'app_id' => config('services.tiktok.app_id'),
            'redirect_uri' => config('services.tiktok.redirect_uri') ?: route('tiktok.oauth.callback'),
            'state' => $state,
            'scope' => config('services.tiktok.scopes'),
        ]));
    }

    public function callback(Request $request, TikTokService $tiktok): RedirectResponse
    {
        $state = $request->string('state')->toString();
        Log::info('TikTok OAuth callback received.', [
            'parameter_names' => array_keys($request->query()),
            'has_state' => $state !== '',
            'has_auth_code' => $request->filled('auth_code'),
            'has_code' => $request->filled('code'),
            'has_error' => $request->filled('error'),
        ]);

        $sessionState = $request->session()->pull('tiktok.oauth_state');
        try {
            $stateData = json_decode(Crypt::decryptString($state), true, 512, JSON_THROW_ON_ERROR);
        } catch (Throwable) {
            if (! is_string($sessionState) || ! hash_equals($sessionState, $state) || $request->user() === null) {
                return redirect()->route('tiktokchat.show')->withErrors(['connection' => 'TikTok authorization could not be verified.']);
            }

            $stateData = ['user_id' => $request->user()->getKey()];
        }

        if (! is_array($stateData) || blank($stateData['user_id'] ?? null)) {
            return redirect()->route('tiktokchat.show')->withErrors(['connection' => 'TikTok authorization could not be verified.']);
        }

        if (is_string($sessionState) && ! hash_equals($sessionState, $state)) {
            return redirect()->route('tiktokchat.show')->withErrors(['connection' => 'TikTok authorization could not be verified.']);
        }

        $user = User::query()->find($stateData['user_id']);
        if ($user === null) {
            return redirect()->route('login')->withErrors(['connection' => 'TikTok authorization user was not found.']);
        }

        $code = $request->string('auth_code')->toString() ?: $request->string('code')->toString();
        if ($request->filled('error') || blank($code)) {
            Log::warning('TikTok OAuth callback did not include an authorization code.', [
                'user_id' => $user->getKey(),
                'has_provider_error' => $request->filled('error'),
                'parameter_names' => array_keys($request->query()),
            ]);

            return redirect()->route('tiktokchat.show')->withErrors(['connection' => 'TikTok authorization was declined or cancelled.']);
        }

        try {
            $data = $tiktok->exchangeCode($code);
        } catch (Throwable $exception) {
            Log::warning('TikTok OAuth token exchange failed.', [
                'user_id' => $user->getKey(),
                'exception_class' => $exception::class,
            ]);
            report($exception);

            return redirect()->route('tiktokchat.show')->withErrors(['connection' => 'TikTok could not complete authorization.']);
        }

        if (blank($data['access_token'] ?? null)) {
            Log::warning('TikTok OAuth response did not contain an access token.', [
                'user_id' => $user->getKey(),
                'response_data_keys' => array_keys($data),
            ]);

            return redirect()->route('tiktokchat.show')->withErrors(['connection' => 'TikTok did not return an access token.']);
        }

        TikTokConnection::query()->updateOrCreate(
            ['user_id' => $user->getKey()],
            [
                'access_token' => $data['access_token'],
                'refresh_token' => $data['refresh_token'] ?? null,
                'tiktok_open_id' => $data['open_id'] ?? null,
                'advertiser_id' => config('services.tiktok.advertiser_id'),
                'token_expires_at' => isset($data['expires_in']) ? now()->addSeconds((int) $data['expires_in']) : null,
                'refresh_token_expires_at' => isset($data['refresh_token_expires_in']) ? now()->addSeconds((int) $data['refresh_token_expires_in']) : null,
            ],
        );

        return redirect()->route('tiktokchat.show')->with('status', 'TikTok Ads connected.');
    }
}
