<?php

declare(strict_types=1);

namespace App\Services\Audit;

use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Models\RestoreRun;
use App\Models\SupportRequest;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Workspace;
use App\Support\Audit\AuditActionId;
use App\Support\Audit\AuditActorSnapshot;
use App\Support\Audit\AuditActorType;
use App\Support\Audit\AuditTargetSnapshot;
use Carbon\CarbonImmutable;
use InvalidArgumentException;

class WorkspaceAuditLogger
{
    public function __construct(
        private readonly AuditRecorder $auditRecorder,
    ) {}

    public function log(
        Workspace $workspace,
        string|AuditActionId $action,
        array $context = [],
        User|PlatformUser|null $actor = null,
        string $status = 'success',
        ?string $resourceType = null,
        ?string $resourceId = null,
        ?int $actorId = null,
        ?string $actorEmail = null,
        ?string $actorName = null,
        ?AuditActorType $actorType = null,
        ?string $targetLabel = null,
        ?string $summary = null,
        ?int $operationRunId = null,
        ?Tenant $tenant = null,
    ): \App\Models\AuditLog {
        $resolvedActor = match (true) {
            $actor instanceof User => AuditActorSnapshot::human($actor),
            $actor instanceof PlatformUser => AuditActorSnapshot::platform($actor),
            default => AuditActorSnapshot::fromLegacy(
                type: $actorType ?? AuditActorType::infer($action instanceof AuditActionId ? $action->value : $action, $actorId, $actorEmail, $actorName, $context),
                id: $actorId,
                email: $actorEmail,
                label: $actorName,
            ),
        };

        return $this->auditRecorder->record(
            action: $action,
            context: $context,
            workspace: $workspace,
            tenant: $tenant,
            actor: $resolvedActor,
            target: new AuditTargetSnapshot(
                type: $resourceType,
                id: $resourceId,
                label: $targetLabel,
            ),
            outcome: $status,
            recordedAt: CarbonImmutable::now(),
            summary: $summary,
            operationRunId: $operationRunId,
        );
    }

    /**
     * @param  array<string, mixed>  $context
     */
    public function logTenantLifecycleAction(
        Tenant $tenant,
        string|AuditActionId $action,
        array $context = [],
        User|PlatformUser|null $actor = null,
        string $status = 'success',
        ?string $summary = null,
    ): \App\Models\AuditLog {
        return $this->log(
            workspace: $tenant->workspace,
            action: $action,
            context: $context,
            actor: $actor,
            status: $status,
            resourceType: 'tenant',
            resourceId: (string) $tenant->getKey(),
            targetLabel: $tenant->name,
            summary: $summary,
            tenant: $tenant,
        );
    }

    /**
     * @param  array<string, mixed>  $bundle
     */
    public function logSupportDiagnosticsOpened(
        Tenant $tenant,
        string $contextType,
        array $bundle,
        User|PlatformUser|null $actor = null,
        ?OperationRun $operationRun = null,
    ): \App\Models\AuditLog {
        $sectionCount = is_array($bundle['sections'] ?? null) ? count($bundle['sections']) : 0;
        $referenceCount = collect($bundle['sections'] ?? [])
            ->sum(static fn (mixed $section): int => is_array($section) && is_array($section['references'] ?? null)
                ? count($section['references'])
                : 0);

        return $this->log(
            workspace: $tenant->workspace,
            action: AuditActionId::SupportDiagnosticsOpened,
            context: [
                'context_type' => $contextType,
                'redaction_mode' => 'default_redacted',
                'section_count' => $sectionCount,
                'reference_count' => $referenceCount,
                'primary_context_id' => $operationRun instanceof OperationRun
                    ? (string) $operationRun->getKey()
                    : (string) $tenant->getKey(),
            ],
            actor: $actor,
            status: 'success',
            resourceType: 'support_diagnostic_bundle',
            resourceId: $operationRun instanceof OperationRun
                ? 'operation_run:'.$operationRun->getKey()
                : 'tenant:'.$tenant->getKey(),
            targetLabel: $operationRun instanceof OperationRun
                ? 'Support diagnostics for operation #'.$operationRun->getKey()
                : 'Support diagnostics for '.$tenant->name,
            summary: $operationRun instanceof OperationRun
                ? 'Support diagnostics opened for operation #'.$operationRun->getKey()
                : 'Support diagnostics opened for '.$tenant->name,
            operationRunId: $operationRun instanceof OperationRun ? (int) $operationRun->getKey() : null,
            tenant: $tenant,
        );
    }

    /**
     * @param  array<string, mixed>  $preflight
     */
    public function logCrossTenantPromotionPreflightGenerated(
        Workspace $workspace,
        Tenant $sourceTenant,
        Tenant $targetTenant,
        array $preflight,
        User|PlatformUser|null $actor = null,
    ): \App\Models\AuditLog {
        $summary = is_array($preflight['summary'] ?? null) ? $preflight['summary'] : [];

        return $this->log(
            workspace: $workspace,
            action: AuditActionId::CrossTenantPromotionPreflightGenerated,
            context: [
                'source_tenant_id' => (int) $sourceTenant->getKey(),
                'source_tenant_name' => (string) $sourceTenant->name,
                'target_tenant_id' => (int) $targetTenant->getKey(),
                'target_tenant_name' => (string) $targetTenant->name,
                'ready_count' => (int) ($summary['ready'] ?? 0),
                'blocked_count' => (int) ($summary['blocked'] ?? 0),
                'manual_mapping_required_count' => (int) ($summary['manual_mapping_required'] ?? 0),
                'total_count' => (int) ($summary['total'] ?? 0),
                'blocked_reason_counts' => is_array($preflight['blockedReasonCounts'] ?? null)
                    ? $preflight['blockedReasonCounts']
                    : [],
            ],
            actor: $actor,
            status: 'success',
            resourceType: 'cross_tenant_promotion_preflight',
            resourceId: sprintf('%s:%s', $sourceTenant->getKey(), $targetTenant->getKey()),
            targetLabel: $sourceTenant->name.' -> '.$targetTenant->name,
            summary: 'Cross-tenant promotion preflight generated for '.$sourceTenant->name.' -> '.$targetTenant->name,
        );
    }

    /**
     * @param  array<string, mixed>  $plan
     */
    public function logCrossTenantPromotionExecutionQueued(
        Workspace $workspace,
        Tenant $sourceTenant,
        Tenant $targetTenant,
        OperationRun $operationRun,
        array $plan,
        User|PlatformUser|null $actor = null,
    ): \App\Models\AuditLog {
        $summary = is_array($plan['summary'] ?? null) ? $plan['summary'] : [];

        return $this->log(
            workspace: $workspace,
            action: AuditActionId::CrossTenantPromotionExecutionQueued,
            context: [
                'source_tenant_id' => (int) $sourceTenant->getKey(),
                'source_tenant_name' => (string) $sourceTenant->name,
                'target_tenant_id' => (int) $targetTenant->getKey(),
                'target_tenant_name' => (string) $targetTenant->name,
                'selection' => is_array($plan['selection'] ?? null) ? $plan['selection'] : [],
                'ready_count' => (int) ($summary['ready'] ?? 0),
                'excluded_count' => (int) ($summary['excluded'] ?? 0),
                'created_count' => (int) ($summary['created'] ?? 0),
                'updated_count' => (int) ($summary['updated'] ?? 0),
            ],
            actor: $actor,
            status: 'queued',
            resourceType: 'operation_run',
            resourceId: (string) $operationRun->getKey(),
            targetLabel: $sourceTenant->name.' -> '.$targetTenant->name,
            summary: 'Cross-tenant promotion execution queued for '.$sourceTenant->name.' -> '.$targetTenant->name,
            operationRunId: (int) $operationRun->getKey(),
            tenant: $targetTenant,
        );
    }

    /**
     * @param  array<string, int>  $summaryCounts
     */
    public function logCrossTenantPromotionExecutionCompleted(
        OperationRun $operationRun,
        ?int $sourceTenantId,
        Tenant $targetTenant,
        array $summaryCounts,
        ?RestoreRun $restoreRun = null,
    ): \App\Models\AuditLog {
        $context = is_array($operationRun->context) ? $operationRun->context : [];
        $sourceTenantName = is_string($context['source_tenant_name'] ?? null)
            ? (string) $context['source_tenant_name']
            : null;

        return $this->log(
            workspace: $targetTenant->workspace,
            action: AuditActionId::CrossTenantPromotionExecutionCompleted,
            context: [
                'source_tenant_id' => $sourceTenantId,
                'source_tenant_name' => $sourceTenantName,
                'target_tenant_id' => (int) $targetTenant->getKey(),
                'target_tenant_name' => (string) $targetTenant->name,
                'summary_counts' => $summaryCounts,
                'restore_run_id' => $restoreRun?->getKey(),
                'operation_outcome' => (string) $operationRun->outcome,
            ],
            status: match ((string) $operationRun->outcome) {
                'failed' => 'failed',
                'partially_succeeded' => 'partial',
                default => 'success',
            },
            resourceType: 'operation_run',
            resourceId: (string) $operationRun->getKey(),
            targetLabel: ($sourceTenantName !== null ? $sourceTenantName.' -> ' : '').$targetTenant->name,
            summary: 'Cross-tenant promotion execution completed for '.(($sourceTenantName !== null ? $sourceTenantName.' -> ' : '')).$targetTenant->name,
            operationRunId: (int) $operationRun->getKey(),
            tenant: $targetTenant,
        );
    }

    public function logSupportRequestCreated(
        SupportRequest $supportRequest,
        User|PlatformUser|null $actor = null,
    ): \App\Models\AuditLog {
        $supportRequest->loadMissing(['tenant.workspace']);

        $tenant = $supportRequest->tenant;

        if (! $tenant instanceof Tenant) {
            throw new InvalidArgumentException('Support requests must belong to a tenant.');
        }

        return $this->log(
            workspace: $tenant->workspace,
            action: AuditActionId::SupportRequestCreated,
            context: [
                'internal_reference' => $supportRequest->internal_reference,
                'primary_context_type' => $supportRequest->primary_context_type,
                'primary_context_id' => $supportRequest->primary_context_type === SupportRequest::PRIMARY_CONTEXT_OPERATION_RUN
                    ? (string) $supportRequest->operation_run_id
                    : (string) $tenant->getKey(),
                'attachment_mode' => $supportRequest->attachment_mode,
                'redaction_mode' => (string) data_get($supportRequest->context_envelope, 'redaction_mode', 'default_redacted'),
            ],
            actor: $actor,
            status: 'success',
            resourceType: 'support_request',
            resourceId: (string) $supportRequest->getKey(),
            targetLabel: $supportRequest->internal_reference,
            summary: 'Support request created for '.$supportRequest->internal_reference,
            operationRunId: $supportRequest->operation_run_id !== null ? (int) $supportRequest->operation_run_id : null,
            tenant: $tenant,
        );
    }

    public function logSupportRequestExternalTicketCreated(
        SupportRequest $supportRequest,
        User|PlatformUser|null $actor = null,
    ): \App\Models\AuditLog {
        return $this->logSupportRequestExternalHandoff(
            supportRequest: $supportRequest,
            actor: $actor,
            action: AuditActionId::SupportRequestExternalTicketCreated,
            status: 'success',
            summaryPrefix: 'External ticket created for support request ',
        );
    }

    public function logSupportRequestExternalTicketLinked(
        SupportRequest $supportRequest,
        User|PlatformUser|null $actor = null,
    ): \App\Models\AuditLog {
        return $this->logSupportRequestExternalHandoff(
            supportRequest: $supportRequest,
            actor: $actor,
            action: AuditActionId::SupportRequestExternalTicketLinked,
            status: 'success',
            summaryPrefix: 'External ticket linked for support request ',
        );
    }

    public function logSupportRequestExternalHandoffFailed(
        SupportRequest $supportRequest,
        User|PlatformUser|null $actor = null,
    ): \App\Models\AuditLog {
        return $this->logSupportRequestExternalHandoff(
            supportRequest: $supportRequest,
            actor: $actor,
            action: AuditActionId::SupportRequestExternalHandoffFailed,
            status: 'failed',
            summaryPrefix: 'External handoff failed for support request ',
        );
    }

    private function logSupportRequestExternalHandoff(
        SupportRequest $supportRequest,
        User|PlatformUser|null $actor,
        AuditActionId $action,
        string $status,
        string $summaryPrefix,
    ): \App\Models\AuditLog {
        $supportRequest->loadMissing(['tenant.workspace']);

        $tenant = $supportRequest->tenant;

        if (! $tenant instanceof Tenant) {
            throw new InvalidArgumentException('Support requests must belong to a tenant.');
        }

        $metadata = [
            'internal_reference' => $supportRequest->internal_reference,
            'primary_context_type' => $supportRequest->primary_context_type,
            'primary_context_id' => $supportRequest->primary_context_type === SupportRequest::PRIMARY_CONTEXT_OPERATION_RUN
                ? (string) $supportRequest->operation_run_id
                : (string) $tenant->getKey(),
            'external_handoff_mode' => $supportRequest->external_handoff_mode,
            'external_ticket_reference' => $supportRequest->external_ticket_reference,
        ];

        if ($supportRequest->external_handoff_failure_summary !== null) {
            $metadata['external_handoff_failure_summary'] = $supportRequest->external_handoff_failure_summary;
        }

        return $this->log(
            workspace: $tenant->workspace,
            action: $action,
            context: $metadata,
            actor: $actor,
            status: $status,
            resourceType: 'support_request',
            resourceId: (string) $supportRequest->getKey(),
            targetLabel: $supportRequest->internal_reference,
            summary: $summaryPrefix.$supportRequest->internal_reference,
            operationRunId: $supportRequest->operation_run_id !== null ? (int) $supportRequest->operation_run_id : null,
            tenant: $tenant,
        );
    }
}
