<?php

namespace App\Http\Controllers;

use App\Models\GoogleAdsCampaign;
use App\Models\GoogleAdsConnection;
use App\Models\GoogleAdsDraft;
use App\Services\GoogleAdsService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Throwable;

class GoogleAdsController extends Controller
{
    public function setup(Request $request): View
    {
        return view('google-ads.setup', ['connection' => GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first()]);
    }

    public function saveSetup(Request $request): RedirectResponse
    {
        $connection = GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first();
        abort_unless($connection !== null, 404);
        $validated = $request->validate([
            'customer_id' => ['required', 'string'],
            'manager_customer_id' => ['nullable', 'string'],
        ]);

        if (! in_array($validated['customer_id'], $connection->available_customer_ids ?? [], true)) {
            return back()->withErrors(['customer_id' => 'Choose an account returned by Google.']);
        }
        if (filled($validated['manager_customer_id'] ?? null)
            && ! in_array($validated['manager_customer_id'], $connection->available_customer_ids ?? [], true)) {
            return back()->withErrors(['manager_customer_id' => 'Choose a manager account returned by Google.']);
        }
        $connection->customer_id = $validated['customer_id'];
        $connection->manager_customer_id = filled($validated['manager_customer_id'] ?? null)
            ? $validated['manager_customer_id']
            : null;
        $connection->save();

        return to_route('google-ads.setup')->with('status', 'Google Ads account selected.');
    }

    public function fetchChildAccounts(Request $request, GoogleAdsService $googleAds): RedirectResponse
    {
        $connection = GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first();
        abort_unless($connection !== null, 404);

        try {
            $childCustomerIds = $googleAds->fetchChildAccounts($connection);
        } catch (Throwable $exception) {
            report($exception);

            return back()->withErrors(['connection' => 'Google Ads child accounts could not be loaded: '.$exception->getMessage()]);
        }

        $connection->available_customer_ids = collect($connection->available_customer_ids ?? [])
            ->merge($childCustomerIds)
            ->unique()
            ->values()
            ->all();
        $connection->save();

        return to_route('google-ads.setup')->with('status', count($childCustomerIds).' child Google Ads account(s) loaded.');
    }

    public function authorize(Request $request): RedirectResponse
    {
        $connection = GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first();

        if ($connection && filled($connection->customer_id) && filled($connection->refresh_token)) {
            return to_route('google-ads.setup')->with('status', 'Google Ads is already connected for this account.');
        }

        if (! $this->hasOAuthConfiguration()) {
            return to_route('google-ads.setup')->withErrors(['connection' => 'Google Ads OAuth is not configured on this server. Add the GOOGLE_ADS environment values first.']);
        }

        $state = Str::random(40);
        $request->session()->put('google_ads.oauth_state', $state);

        return redirect()->away('https://accounts.google.com/o/oauth2/v2/auth?'.http_build_query([
            'client_id' => GoogleAdsConnection::oauthClientId(),
            'redirect_uri' => route('google-ads.oauth.callback'),
            'response_type' => 'code',
            'scope' => 'https://www.googleapis.com/auth/adwords openid email',
            'access_type' => 'offline',
            'prompt' => 'consent',
            'state' => $state,
        ]));
    }

    public function callback(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'code' => ['required_without:error', 'string'],
            'state' => ['required', 'string'],
            'error' => ['nullable', 'string'],
        ]);

        $state = $request->session()->pull('google_ads.oauth_state');
        if (! is_string($state) || ! hash_equals($state, $validated['state'])) {
            return to_route('google-ads.setup')->withErrors(['connection' => 'Google Ads authorization could not be verified. Please try again.']);
        }
        if (filled($validated['error'] ?? null)) {
            return to_route('google-ads.setup')->withErrors(['connection' => 'Google Ads authorization was declined or cancelled.']);
        }

        if (! $this->hasOAuthConfiguration()) {
            return to_route('google-ads.setup')->withErrors(['connection' => 'Google Ads OAuth is not configured on this server.']);
        }

        try {
            $token = Http::asForm()
                ->connectTimeout(3)
                ->timeout(10)
                ->post('https://oauth2.googleapis.com/token', [
                    'code' => $validated['code'],
                    'client_id' => GoogleAdsConnection::oauthClientId(),
                    'client_secret' => GoogleAdsConnection::oauthClientSecret(),
                    'redirect_uri' => route('google-ads.oauth.callback'),
                    'grant_type' => 'authorization_code',
                ])
                ->throw()
                ->json();
        } catch (Throwable $exception) {
            report($exception);

            return to_route('google-ads.setup')->withErrors(['connection' => 'Google could not complete authorization. Please try again.']);
        }

        if (! is_string($token['refresh_token'] ?? null) || blank($token['refresh_token']) || ! is_string($token['access_token'] ?? null)) {
            return to_route('google-ads.setup')->withErrors(['connection' => 'Google did not return a refresh token. Reconnect with consent enabled.']);
        }

        try {
            $profile = Http::withToken($token['access_token'])->connectTimeout(3)->timeout(10)->get('https://openidconnect.googleapis.com/v1/userinfo')->throw()->json();
            $customers = Http::withToken($token['access_token'])
                ->withHeaders(['developer-token' => GoogleAdsConnection::developerToken()])
                ->connectTimeout(3)
                ->timeout(10)
                ->get('https://googleads.googleapis.com/'.config('services.google_ads.api_version').'/customers:listAccessibleCustomers')
                ->throw()
                ->json();
        } catch (Throwable $exception) {
            report($exception);

            return to_route('google-ads.setup')->withErrors(['connection' => 'Google authorization succeeded, but the Ads accounts could not be loaded. Please try again.']);
        }

        $customerIds = collect($customers['resourceNames'] ?? [])->map(function (string $resourceName): string {
            return str_replace('customers/', '', $resourceName);
        })->filter()->values()->all();
        $connection = GoogleAdsConnection::query()->firstOrNew(['user_id' => $request->user()->id]);
        $connection->google_email = is_string($profile['email'] ?? null) ? $profile['email'] : null;
        $connection->available_customer_ids = $customerIds;
        $connection->customer_id = count($customerIds) === 1 ? $customerIds[0] : null;
        $connection->refresh_token = $token['refresh_token'];
        $connection->save();

        return to_route('google-ads.setup')->with('status', count($customerIds) === 1 ? 'Google Ads account connected.' : 'Google account connected. Select an Ads account below.');
    }

    public function create(Request $request): View
    {
        return view('google-ads.create', [
            'connection' => GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first(),
            'connectionReady' => GoogleAdsConnection::query()->where('user_id', $request->user()->id)->whereNotNull('customer_id')->whereNotNull('refresh_token')->exists(),
            'drafts' => GoogleAdsDraft::query()->where('user_id', $request->user()->id)->latest()->limit(5)->get(),
        ]);
    }

    public function campaigns(Request $request): View
    {
        return view('google-ads.campaigns', [
            'connection' => GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first(),
            'campaigns' => GoogleAdsCampaign::query()->where('user_id', $request->user()->id)->latest()->get(),
        ]);
    }

    public function storeCampaign(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'status' => ['required', 'in:draft,enabled,paused'],
            'daily_budget' => ['nullable', 'numeric', 'min:0'],
            'ad_group_name' => ['nullable', 'string', 'max:255'],
            'final_url' => ['nullable', 'url', 'max:2048'],
            'notes' => ['nullable', 'string', 'max:5000'],
        ]);

        $connection = GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first();
        GoogleAdsCampaign::create($validated + [
            'user_id' => $request->user()->id,
            'google_ads_connection_id' => $connection ? $connection->id : null,
        ]);

        return to_route('google-ads.campaigns')->with('status', 'Campaign test record created.');
    }

    public function updateCampaign(Request $request, GoogleAdsCampaign $campaign, GoogleAdsService $googleAds): RedirectResponse
    {
        abort_unless($campaign->user_id === $request->user()->id, 404);
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'status' => ['required', 'in:draft,enabled,paused'],
            'daily_budget' => ['nullable', 'numeric', 'min:0'],
            'ad_group_name' => ['nullable', 'string', 'max:255'],
            'final_url' => ['nullable', 'url', 'max:2048'],
            'notes' => ['nullable', 'string', 'max:5000'],
        ]);
        if ($campaign->google_campaign_id) {
            $connection = GoogleAdsConnection::query()->where('user_id', $request->user()->id)->find($campaign->google_ads_connection_id);
            abort_unless($connection !== null, 404);

            try {
                $googleAds->updateCampaign($connection, $campaign, $validated);
            } catch (Throwable $exception) {
                report($exception);

                return back()->withInput()->withErrors(['connection' => 'Google Ads could not update the campaign: '.$exception->getMessage()]);
            }
        }

        $campaign->update($validated);

        return to_route('google-ads.campaigns')->with('status', 'Campaign test record updated.');
    }

    public function destroyCampaign(Request $request, GoogleAdsCampaign $campaign): RedirectResponse
    {
        abort_unless($campaign->user_id === $request->user()->id, 404);
        $campaign->delete();

        return to_route('google-ads.campaigns')->with('status', 'Campaign test record deleted.');
    }

    public function store(Request $request, GoogleAdsService $googleAds): RedirectResponse
    {
        $validated = $request->validate([
            'customer_id' => ['required', 'string'],
            'campaign_name' => ['required', 'string', 'max:255'], 'ad_group_name' => ['required', 'string', 'max:255'],
            'headline_1' => ['required', 'string', 'max:30'], 'headline_2' => ['required', 'string', 'max:30'], 'headline_3' => ['required', 'string', 'max:30'],
            'description_1' => ['required', 'string', 'max:90'], 'description_2' => ['required', 'string', 'max:90'],
            'final_url' => ['required', 'url', 'max:2048'], 'keywords' => ['required', 'string', 'max:1000'],
            'daily_budget' => ['required', 'numeric', 'min:0.01', 'max:1000000'],
        ]);
        $keywords = collect(explode(',', $validated['keywords']))
            ->map(function (string $keyword): string {
                return trim($keyword);
            })
            ->filter()
            ->values();
        $connection = GoogleAdsConnection::query()->where('user_id', $request->user()->id)->first();

        if (! $connection?->refresh_token) {
            return to_route('google-ads.create')->withInput()->withErrors(['connection' => 'Connect Google Ads and select a customer account before creating a live campaign.']);
        }
        if (! in_array($validated['customer_id'], $connection->available_customer_ids ?? [], true)
            || $validated['customer_id'] === $connection->manager_customer_id) {
            return back()->withInput()->withErrors(['customer_id' => 'Choose an enabled child account.']);
        }
        $connection->customer_id = $validated['customer_id'];
        $connection->save();

        try {
            $googleCampaign = $googleAds->createPausedCampaign($connection, [
                'name' => $validated['campaign_name'],
                'daily_budget' => $validated['daily_budget'],
                'ad_group_name' => $validated['ad_group_name'],
                'headlines' => [$validated['headline_1'], $validated['headline_2'], $validated['headline_3']],
                'descriptions' => [$validated['description_1'], $validated['description_2']],
                'final_url' => $validated['final_url'],
                'keywords' => $keywords->implode(', '),
            ]);
        } catch (Throwable $exception) {
            report($exception);

            return back()->withInput()->withErrors(['connection' => 'Google Ads could not create the campaign: '.$exception->getMessage()]);
        }

        GoogleAdsCampaign::create([
            'user_id' => $request->user()->id,
            'google_ads_connection_id' => $connection->id,
            'google_campaign_id' => $googleCampaign['campaign_id'],
            'google_campaign_budget_id' => $googleCampaign['budget_id'],
            'google_ad_group_id' => $googleCampaign['ad_group_id'],
            'google_ad_id' => $googleCampaign['ad_id'],
            'name' => $validated['campaign_name'],
            'status' => 'paused',
            'daily_budget' => $validated['daily_budget'],
            'ad_group_name' => $validated['ad_group_name'],
            'final_url' => $validated['final_url'],
            'notes' => implode(', ', $keywords->all()),
        ]);

        return to_route('google-ads.campaigns')->with('status', 'Paused Google Ads campaign created successfully.');
    }

    private function hasOAuthConfiguration(): bool
    {
        return filled(GoogleAdsConnection::developerToken())
            && filled(GoogleAdsConnection::oauthClientId())
            && filled(GoogleAdsConnection::oauthClientSecret());
    }
}
