<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\ManagedEnvironment;
use App\Services\Intune\AuditLogger;
use App\Services\Providers\ProviderConnectionClassificationResult;
use App\Services\Providers\ProviderConnectionClassifier;
use App\Support\Providers\ProviderConnectionType;
use App\Support\Providers\ProviderCredentialKind;
use App\Support\Providers\ProviderCredentialSource;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;

class ClassifyProviderConnections extends Command
{
    protected $signature = 'tenantpilot:provider-connections:classify
        {--tenant= : Restrict to a tenant id, external id, or tenant guid}
        {--connection= : Restrict to a single provider connection id}
        {--provider=microsoft : Restrict to one provider}
        {--chunk=100 : Chunk size for large write runs}
        {--write : Persist the classification results}';

    protected $description = 'Classify legacy provider connections into platform, dedicated, or review-required outcomes.';

    public function handle(ProviderConnectionClassifier $classifier): int
    {
        $query = $this->query();
        $write = (bool) $this->option('write');
        $chunkSize = max(1, (int) $this->option('chunk'));

        $candidateCount = (clone $query)->count();

        if ($candidateCount === 0) {
            $this->info('No provider connections matched the classification scope.');

            return self::SUCCESS;
        }

        $tenantCounts = (clone $query)
            ->selectRaw('managed_environment_id, count(*) as aggregate')
            ->groupBy('managed_environment_id')
            ->pluck('aggregate', 'managed_environment_id')
            ->map(static fn (mixed $count): int => (int) $count)
            ->all();

        $startedTenants = [];
        $classifiedCount = 0;
        $appliedCount = 0;
        $reviewRequiredCount = 0;

        $query
            ->with(['tenant', 'credential'])
            ->orderBy('id')
            ->chunkById($chunkSize, function ($connections) use (
                $classifier,
                $write,
                $tenantCounts,
                &$startedTenants,
                &$classifiedCount,
                &$appliedCount,
                &$reviewRequiredCount,
            ): void {
                foreach ($connections as $connection) {
                    $classifiedCount++;

                    $result = $classifier->classify(
                        $connection,
                        source: 'tenantpilot:provider-connections:classify',
                    );

                    if ($result->reviewRequired) {
                        $reviewRequiredCount++;
                    }

                    if (! $write) {
                        continue;
                    }

                    $tenant = $connection->tenant;

                    if (! $tenant instanceof ManagedEnvironment) {
                        $this->warn(sprintf('Skipping provider connection #%d without tenant context.', (int) $connection->getKey()));

                        continue;
                    }

                    $tenantKey = (int) $tenant->getKey();

                    if (! array_key_exists($tenantKey, $startedTenants)) {
                        $this->auditStart($tenant, $tenantCounts[$tenantKey] ?? 0);
                        $startedTenants[$tenantKey] = true;
                    }

                    $connection = $this->applyClassification($connection, $result);
                    $this->auditApplied($tenant, $connection, $result);
                    $appliedCount++;
                }
            });

        if ($write) {
            $this->info(sprintf('Applied classifications: %d', $appliedCount));
        } else {
            $this->info(sprintf('Dry-run classifications: %d', $classifiedCount));
        }

        $this->info(sprintf('Review required: %d', $reviewRequiredCount));
        $this->info(sprintf('Mode: %s', $write ? 'write' : 'dry-run'));

        return self::SUCCESS;
    }

    private function query(): Builder
    {
        $query = ProviderConnection::query()
            ->where('provider', (string) $this->option('provider'));

        $tenantOption = $this->option('tenant');

        if (is_string($tenantOption) && trim($tenantOption) !== '') {
            $tenant = ManagedEnvironment::query()
                ->forTenant(trim($tenantOption))
                ->firstOrFail();

            $query->where('managed_environment_id', (int) $tenant->getKey());
        }

        $connectionOption = $this->option('connection');

        if (is_numeric($connectionOption)) {
            $query->whereKey((int) $connectionOption);
        }

        return $query;
    }

    private function applyClassification(
        ProviderConnection $connection,
        ProviderConnectionClassificationResult $result,
    ): ProviderConnection {
        DB::transaction(function () use ($connection, $result): void {
            $connection->forceFill(
                $connection->classificationProjection($result)
            )->save();

            $credential = $connection->credential;

            if (! $credential instanceof ProviderCredential) {
                return;
            }

            $updates = [];

            if (
                $result->suggestedConnectionType === ProviderConnectionType::Dedicated
                && $credential->source === null
            ) {
                $updates['source'] = ProviderCredentialSource::LegacyMigrated->value;
            }

            if ($credential->credential_kind === null && $credential->type === ProviderCredentialKind::ClientSecret->value) {
                $updates['credential_kind'] = ProviderCredentialKind::ClientSecret->value;
            }

            if ($updates !== []) {
                $credential->forceFill($updates)->save();
            }
        });

        return $connection->fresh(['tenant', 'credential']);
    }

    private function auditStart(ManagedEnvironment $tenant, int $candidateCount): void
    {
        app(AuditLogger::class)->log(
            tenant: $tenant,
            action: 'provider_connection.migration_classification_started',
            context: [
                'metadata' => [
                    'source' => 'tenantpilot:provider-connections:classify',
                    'provider' => 'microsoft',
                    'candidate_count' => $candidateCount,
                    'write' => true,
                ],
            ],
            resourceType: 'tenant',
            resourceId: (string) $tenant->getKey(),
            status: 'success',
        );
    }

    private function auditApplied(
        ManagedEnvironment $tenant,
        ProviderConnection $connection,
        ProviderConnectionClassificationResult $result,
    ): void {
        $effectiveApp = $connection->effectiveAppMetadata();

        app(AuditLogger::class)->log(
            tenant: $tenant,
            action: 'provider_connection.migration_classification_applied',
            context: [
                'metadata' => [
                    'source' => 'tenantpilot:provider-connections:classify',
                    'workspace_id' => (int) $connection->workspace_id,
                    'provider_connection_id' => (int) $connection->getKey(),
                    'provider' => (string) $connection->provider,
                    'entra_tenant_id' => (string) $connection->entra_tenant_id,
                    'connection_type' => $connection->connection_type->value,
                    'migration_review_required' => $connection->migration_review_required,
                    'legacy_identity_result' => $result->suggestedConnectionType->value,
                    'effective_app_id' => $effectiveApp['app_id'],
                    'effective_app_source' => $effectiveApp['source'],
                    'signals' => $result->signals,
                ],
            ],
            resourceType: 'provider_connection',
            resourceId: (string) $connection->getKey(),
            status: 'success',
        );
    }
}
