<?php

namespace App\Http\Controllers;

use App\Models\Tenant;
use App\Services\Graph\GraphClientInterface;
use App\Services\Intune\AuditLogger;
use App\Services\Intune\TenantConfigService;
use App\Services\Intune\TenantPermissionService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;

class AdminConsentCallbackController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(
        Request $request,
        AuditLogger $auditLogger,
        TenantConfigService $configService,
        TenantPermissionService $permissionService,
        GraphClientInterface $graphClient
    ): View {
        $expectedState = $request->session()->pull('tenant_onboard_state');
        $tenantKey = $request->string('tenant')->toString();
        $state = $request->string('state')->toString();
        $tenantIdentifier = $tenantKey ?: $this->parseState($state);

        if ($expectedState && $expectedState !== $state) {
            abort(ResponseAlias::HTTP_FORBIDDEN, 'Invalid consent state');
        }

        abort_if(empty($tenantIdentifier), 404);

        $tenant = Tenant::withTrashed()
            ->forTenant($tenantIdentifier)
            ->first();

        if ($tenant?->trashed()) {
            $tenant->restore();
        }

        if (! $tenant) {
            $tenant = Tenant::create([
                'tenant_id' => $tenantIdentifier,
                'name' => 'New Tenant',
                'app_client_id' => config('graph.client_id'),
                'app_client_secret' => config('graph.client_secret'),
                'app_status' => 'pending',
            ]);
        }

        $error = $request->string('error')->toString() ?: null;
        $consentGranted = $request->has('admin_consent')
            ? filter_var($request->input('admin_consent'), FILTER_VALIDATE_BOOLEAN)
            : null;

        $status = match (true) {
            $error !== null => 'error',
            $consentGranted === false => 'consent_denied',
            $consentGranted === true => 'ok',
            default => 'pending',
        };

        $tenant->update([
            'app_status' => $status,
            'app_notes' => $error,
        ]);

        $auditLogger->log(
            tenant: $tenant,
            action: 'tenant.consent.callback',
            context: [
                'metadata' => [
                    'status' => $status,
                    'state' => $state,
                    'error' => $error,
                    'consent' => $consentGranted,
                ],
            ],
            status: $status === 'ok' ? 'success' : 'error',
            resourceType: 'tenant',
            resourceId: (string) $tenant->id,
        );

        return view('admin-consent-callback', [
            'tenant' => $tenant,
            'status' => $status,
            'error' => $error,
            'consentGranted' => $consentGranted,
        ]);
    }

    private function handleAuthorizationCodeFlow(
        Request $request,
        AuditLogger $auditLogger,
        TenantConfigService $configService,
        TenantPermissionService $permissionService,
        GraphClientInterface $graphClient
    ): View {
        $expectedState = $request->session()->pull('tenant_onboard_state');
        if ($expectedState && $expectedState !== $request->string('state')->toString()) {
            abort(ResponseAlias::HTTP_FORBIDDEN, 'Invalid consent state');
        }

        $redirectUri = route('admin.consent.callback');

        $token = $this->exchangeAuthorizationCode(
            code: $request->string('code')->toString(),
            redirectUri: $redirectUri
        );

        $tenantId = $token['tenant_id'] ?? null;
        abort_if(empty($tenantId), 500, 'Tenant ID missing from token');

        /** @var Tenant|null $tenant */
        $tenant = Tenant::withTrashed()
            ->forTenant($tenantId)
            ->first();

        if ($tenant?->trashed()) {
            $tenant->restore();
        }

        if (! $tenant) {
            $tenant = Tenant::create([
                'tenant_id' => $tenantId,
                'name' => 'New Tenant',
                'app_client_id' => config('graph.client_id'),
                'app_client_secret' => config('graph.client_secret'),
                'app_status' => 'pending',
            ]);
        }

        $orgResponse = $graphClient->getOrganization([
            'tenant' => $tenant->graphTenantId(),
            'client_id' => $tenant->app_client_id,
            'client_secret' => $tenant->app_client_secret,
        ]);

        if ($orgResponse->successful()) {
            $org = $orgResponse->data ?? [];
            $tenant->update([
                'name' => $org['displayName'] ?? $tenant->name,
                'domain' => $org['verifiedDomains'][0]['name'] ?? $tenant->domain,
            ]);
        }

        $configResult = $configService->testConnectivity($tenant);
        $permissionService->compare($tenant);

        $status = $configResult['success'] ? 'ok' : 'error';

        $tenant->update([
            'app_status' => $status,
            'app_notes' => $configResult['error_message'],
        ]);

        $auditLogger->log(
            tenant: $tenant,
            action: 'tenant.consent.callback',
            context: [
                'metadata' => [
                    'status' => $status,
                    'error' => $configResult['error_message'],
                    'from' => 'authorization_code',
                ],
            ],
            status: $status === 'ok' ? 'success' : 'error',
            resourceType: 'tenant',
            resourceId: (string) $tenant->id,
        );

        return view('admin-consent-callback', [
            'tenant' => $tenant,
            'status' => $status,
            'error' => $configResult['error_message'],
            'consentGranted' => $status === 'ok',
        ]);
    }

    /**
     * @return array{access_token:string,id_token:string,tenant_id:?string}
     */
    private function exchangeAuthorizationCode(string $code, string $redirectUri): array
    {
        $response = Http::asForm()->post('https://login.microsoftonline.com/common/oauth2/v2.0/token', [
            'client_id' => config('graph.client_id'),
            'client_secret' => config('graph.client_secret'),
            'code' => $code,
            'grant_type' => 'authorization_code',
            'redirect_uri' => $redirectUri,
            'scope' => 'https://graph.microsoft.com/.default offline_access openid profile',
        ]);

        if ($response->failed()) {
            abort(ResponseAlias::HTTP_BAD_GATEWAY, 'Failed to exchange code for token');
        }

        $body = $response->json();
        $idToken = $body['id_token'] ?? null;
        $tenantId = $this->parseTenantIdFromToken($idToken);

        return [
            'access_token' => $body['access_token'] ?? '',
            'id_token' => $idToken ?? '',
            'tenant_id' => $tenantId,
        ];
    }

    private function parseTenantIdFromToken(?string $token): ?string
    {
        if (! $token || ! str_contains($token, '.')) {
            return null;
        }

        $parts = explode('.', $token);
        $payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')) ?: '[]', true);

        return $payload['tid'] ?? $payload['tenant'] ?? null;
    }

    private function parseState(?string $state): ?string
    {
        if (empty($state)) {
            return null;
        }

        if (str_contains($state, '|')) {
            [, $value] = explode('|', $state, 2);

            return $value;
        }

        return $state;
    }
}
