<?php

namespace App\Services;

use App\Models\BulkOperationRun;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Intune\AuditLogger;

class BulkOperationService
{
    public function __construct(
        protected AuditLogger $auditLogger
    ) {}

    public function sanitizeFailureReason(string $reason): string
    {
        $reason = trim($reason);

        if ($reason === '') {
            return 'error';
        }

        $lower = mb_strtolower($reason);

        if (
            str_contains($lower, 'bearer ') ||
            str_contains($lower, 'access_token') ||
            str_contains($lower, 'client_secret') ||
            str_contains($lower, 'authorization')
        ) {
            return 'redacted';
        }

        $reason = preg_replace("/\s+/u", ' ', $reason) ?? $reason;

        return mb_substr($reason, 0, 200);
    }

    public function createRun(
        Tenant $tenant,
        User $user,
        string $resource,
        string $action,
        array $itemIds,
        int $totalItems,
        ?string $idempotencyKey = null
    ): BulkOperationRun {
        $effectiveTotalItems = $totalItems;

        if (array_is_list($itemIds)) {
            $effectiveTotalItems = max($totalItems, count($itemIds));
        }

        $run = BulkOperationRun::create([
            'tenant_id' => $tenant->id,
            'user_id' => $user->id,
            'resource' => $resource,
            'action' => $action,
            'idempotency_key' => $idempotencyKey,
            'status' => 'pending',
            'item_ids' => $itemIds,
            'total_items' => $effectiveTotalItems,
            'processed_items' => 0,
            'succeeded' => 0,
            'failed' => 0,
            'skipped' => 0,
            'failures' => [],
        ]);

        $auditLog = $this->auditLogger->log(
            tenant: $tenant,
            action: "bulk.{$resource}.{$action}.created",
            context: [
                'metadata' => [
                    'bulk_run_id' => $run->id,
                    'total_items' => $effectiveTotalItems,
                ],
            ],
            actorId: $user->id,
            actorEmail: $user->email,
            actorName: $user->name,
            resourceType: 'bulk_operation_run',
            resourceId: (string) $run->id
        );

        $run->update(['audit_log_id' => $auditLog->id]);

        return $run;
    }

    public function start(BulkOperationRun $run): void
    {
        $run->update(['status' => 'running']);
    }

    public function recordSuccess(BulkOperationRun $run): void
    {
        $run->increment('processed_items');
        $run->increment('succeeded');
    }

    public function recordFailure(BulkOperationRun $run, string $itemId, string $reason, ?string $reasonCode = null): void
    {
        $reason = $this->sanitizeFailureReason($reason);

        $failures = $run->failures ?? [];
        $failureEntry = [
            'item_id' => $itemId,
            'reason' => $reason,
            'timestamp' => now()->toIso8601String(),
        ];

        if (is_string($reasonCode) && $reasonCode !== '') {
            $failureEntry['reason_code'] = $reasonCode;
        }

        $failures[] = $failureEntry;

        $run->update([
            'failures' => $failures,
            'processed_items' => $run->processed_items + 1,
            'failed' => $run->failed + 1,
        ]);
    }

    public function recordSkipped(BulkOperationRun $run): void
    {
        $run->increment('processed_items');
        $run->increment('skipped');
    }

    public function recordSkippedWithReason(BulkOperationRun $run, string $itemId, string $reason, ?string $reasonCode = null): void
    {
        $reason = $this->sanitizeFailureReason($reason);

        $failures = $run->failures ?? [];
        $failureEntry = [
            'item_id' => $itemId,
            'reason' => $reason,
            'type' => 'skipped',
            'timestamp' => now()->toIso8601String(),
        ];

        if (is_string($reasonCode) && $reasonCode !== '') {
            $failureEntry['reason_code'] = $reasonCode;
        }

        $failures[] = $failureEntry;

        $run->update([
            'failures' => $failures,
            'processed_items' => $run->processed_items + 1,
            'skipped' => $run->skipped + 1,
        ]);
    }

    public function complete(BulkOperationRun $run): void
    {
        $run->refresh();

        if ($run->processed_items > $run->total_items) {
            BulkOperationRun::query()
                ->whereKey($run->id)
                ->update(['total_items' => $run->processed_items]);

            $run->refresh();
        }

        if (! in_array($run->status, ['pending', 'running'], true)) {
            return;
        }

        $failureEntries = collect($run->failures ?? []);
        $hasFailures = $run->failed > 0
            || $failureEntries->contains(fn (array $entry): bool => ($entry['type'] ?? 'failed') !== 'skipped');

        $status = $hasFailures ? 'completed_with_errors' : 'completed';

        $updated = BulkOperationRun::query()
            ->whereKey($run->id)
            ->whereIn('status', ['pending', 'running'])
            ->update(['status' => $status]);

        if ($updated === 0) {
            return;
        }

        $run->refresh();

        $failureEntries = collect($run->failures ?? []);
        $failedReasons = $failureEntries
            ->filter(fn (array $entry) => ($entry['type'] ?? 'failed') !== 'skipped')
            ->groupBy('reason')
            ->map(fn ($group) => $group->count())
            ->all();

        $skippedReasons = $failureEntries
            ->filter(fn (array $entry) => ($entry['type'] ?? null) === 'skipped')
            ->groupBy('reason')
            ->map(fn ($group) => $group->count())
            ->all();

        $this->auditLogger->log(
            tenant: $run->tenant,
            action: "bulk.{$run->resource}.{$run->action}.{$status}",
            context: [
                'metadata' => [
                    'bulk_run_id' => $run->id,
                    'succeeded' => $run->succeeded,
                    'failed' => $run->failed,
                    'skipped' => $run->skipped,
                    'failed_reasons' => $failedReasons,
                    'skipped_reasons' => $skippedReasons,
                ],
            ],
            actorId: $run->user_id,
            resourceType: 'bulk_operation_run',
            resourceId: (string) $run->id
        );
    }

    public function fail(BulkOperationRun $run, string $reason): void
    {
        $run->update(['status' => 'failed']);

        $reason = $this->sanitizeFailureReason($reason);

        $this->auditLogger->log(
            tenant: $run->tenant,
            action: "bulk.{$run->resource}.{$run->action}.failed",
            context: [
                'reason' => $reason,
                'metadata' => [
                    'bulk_run_id' => $run->id,
                ],
            ],
            actorId: $run->user_id,
            status: 'failure',
            resourceType: 'bulk_operation_run',
            resourceId: (string) $run->id
        );
    }

    public function abort(BulkOperationRun $run, string $reason): void
    {
        $run->update(['status' => 'aborted']);

        $reason = $this->sanitizeFailureReason($reason);

        $this->auditLogger->log(
            tenant: $run->tenant,
            action: "bulk.{$run->resource}.{$run->action}.aborted",
            context: [
                'reason' => $reason,
                'metadata' => [
                    'bulk_run_id' => $run->id,
                    'succeeded' => $run->succeeded,
                    'failed' => $run->failed,
                    'skipped' => $run->skipped,
                ],
            ],
            actorId: $run->user_id,
            status: 'failure',
            resourceType: 'bulk_operation_run',
            resourceId: (string) $run->id
        );
    }
}
