<?php

declare(strict_types=1);

namespace App\Services\Install;

use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * Web 安装向导专用日志：写入 {@see config('logging.channels.dberp_install')}，与业务 UI 分离。
 */
final class DBErpInstallLogger
{
    private const CHANNEL = 'dberp_install';

    public function installSessionStarted(string $installId): void
    {
        Log::channel(self::CHANNEL)->info('开始 Web 安装流程', [
            'install_id' => $installId,
            'occurred_at' => now()->toIso8601String(),
        ]);
    }

    public function stageProgress(
        string $installId,
        string $stage,
        string $status,
        string $startedAt,
        string $finishedAt,
        int $durationMs,
        string $userFacingMessage,
        int $warningCount,
        int $errorCount,
    ): void {
        $level = match ($status) {
            WebInstallProgressStatus::Failed->value => 'error',
            WebInstallProgressStatus::Warning->value => 'warning',
            default => 'info',
        };

        Log::channel(self::CHANNEL)->{$level}('安装阶段进度更新', [
            'install_id' => $installId,
            'stage' => $stage,
            'status' => $status,
            'started_at' => $startedAt,
            'finished_at' => $finishedAt,
            'duration_ms' => $durationMs,
            'user_facing_message' => $userFacingMessage,
            'warning_count' => $warningCount,
            'error_count' => $errorCount,
            'occurred_at' => now()->toIso8601String(),
        ]);
    }

    public function phaseEntered(WebInstallPhase $phase): void
    {
        Log::channel(self::CHANNEL)->info('进入安装阶段：'.$phase->labelZh(), [
            'step' => 'phase',
            'phase' => $phase->value,
            'occurred_at' => now()->toIso8601String(),
        ]);
    }

    /**
     * @param  array<string, mixed>  $context
     */
    public function optionalCommandOutcome(
        string $command,
        string $outcome,
        ?int $exitCode,
        string $output,
        array $context = [],
    ): void {
        $payload = array_merge([
            'step' => 'optional_finalizer',
            'command' => $command,
            'outcome' => $outcome,
            'exit_code' => $exitCode,
            'occurred_at' => now()->toIso8601String(),
            'output_snippet' => $this->truncateOutput($output),
        ], $this->sanitizeContext($context));

        $level = match ($outcome) {
            'skipped', 'executed' => 'info',
            'warn_invalid_path', 'failed' => 'warning',
            default => 'notice',
        };

        Log::channel(self::CHANNEL)->{$level}('可选收尾命令：'.$command.'（'.$outcome.'）', $payload);
    }

    /**
     * @param  array<string, mixed>  $context
     */
    public function stepProgress(string $stepKey, string $messageZh, array $context = []): void
    {
        Log::channel(self::CHANNEL)->info($messageZh, array_merge([
            'step' => $stepKey,
            'occurred_at' => now()->toIso8601String(),
        ], $this->sanitizeContext($context)));
    }

    /**
     * @param  array<string, mixed>  $context
     */
    public function installAborted(string $stepKey, string $messageZh, array $context = []): void
    {
        Log::channel(self::CHANNEL)->warning($messageZh, array_merge([
            'step' => $stepKey,
            'occurred_at' => now()->toIso8601String(),
        ], $this->sanitizeContext($context)));
    }

    public function businessRuleBlocked(string $stepKey, string $messageZh): void
    {
        Log::channel(self::CHANNEL)->notice('安装向导业务校验未通过', [
            'step' => $stepKey,
            'message' => $messageZh,
            'occurred_at' => now()->toIso8601String(),
        ]);
    }

    /**
     * @param  array<string, mixed>  $context
     */
    public function technicalFailure(string $stepKey, string $messageZh, Throwable $exception, array $context = []): void
    {
        Log::channel(self::CHANNEL)->error($messageZh, array_merge([
            'step' => $stepKey,
            'occurred_at' => now()->toIso8601String(),
            'exception_class' => $exception::class,
            'exception_message' => $exception->getMessage(),
        ], $this->sanitizeContext($context)));

        Log::channel(self::CHANNEL)->error('安装过程异常堆栈（仅供技术人员排查）', [
            'step' => $stepKey,
            'trace' => $exception->getTraceAsString(),
        ]);
    }

    /**
     * @param  array<string, mixed>  $context
     */
    public function commandFinished(string $stepKey, string $messageZh, int $exitCode, string $output, array $context = []): void
    {
        $payload = array_merge([
            'step' => $stepKey,
            'exit_code' => $exitCode,
            'occurred_at' => now()->toIso8601String(),
            'output_snippet' => $this->truncateOutput($output),
        ], $this->sanitizeContext($context));

        if ($exitCode === 0) {
            Log::channel(self::CHANNEL)->info($messageZh, $payload);
        } else {
            Log::channel(self::CHANNEL)->error($messageZh, $payload);
        }
    }

    /**
     * @param  list<string>  $finalizationWarnings
     */
    public function installCompletedSuccessfully(array $finalizationWarnings = []): void
    {
        $payload = [
            'occurred_at' => now()->toIso8601String(),
            'finalization_warning_count' => count($finalizationWarnings),
        ];

        if ($finalizationWarnings !== []) {
            $payload['finalization_warnings'] = $finalizationWarnings;
            Log::channel(self::CHANNEL)->warning(
                'Web 安装流程已完成（含已安装标记），但存在可选收尾警告',
                $payload,
            );

            return;
        }

        Log::channel(self::CHANNEL)->info('Web 安装流程已全部完成（含已安装标记写入）', $payload);
    }

    /**
     * @param  list<string>  $migrationNames
     */
    public function migrationBatchStarted(
        string $installId,
        array $migrationNames,
        int $pendingCount,
        int $batchSize,
        int $totalCount,
        int $completedCount,
    ): void {
        Log::channel(self::CHANNEL)->info('开始执行迁移批次', [
            'install_id' => $installId,
            'step' => 'migrate_batch',
            'stage' => WebInstallPhase::Migrating->value,
            'migration_total' => $totalCount,
            'migration_completed' => $completedCount,
            'migration_pending' => $pendingCount,
            'batch_size' => $batchSize,
            'executed_migrations' => $migrationNames,
            'occurred_at' => now()->toIso8601String(),
        ]);
    }

    /**
     * @param  list<string>  $executedMigrationNames
     */
    public function migrationBatchFinished(
        string $installId,
        array $executedMigrationNames,
        int $pendingCount,
        float $elapsedSeconds,
        WebInstallMigrationProgress $progress,
    ): void {
        Log::channel(self::CHANNEL)->info('迁移批次执行完成', [
            'install_id' => $installId,
            'step' => 'migrate_batch',
            'stage' => WebInstallPhase::Migrating->value,
            'migration_total' => $progress->total,
            'migration_completed' => $progress->completed,
            'migration_pending' => $progress->pending,
            'batch_size' => $progress->batchSize,
            'executed_migrations' => $executedMigrationNames,
            'elapsed_ms' => (int) round($elapsedSeconds * 1000),
            'occurred_at' => now()->toIso8601String(),
        ]);
    }

    /**
     * @param  list<string>  $attemptedMigrationNames
     */
    public function migrationBatchFailed(
        string $installId,
        Throwable $exception,
        array $attemptedMigrationNames,
        int $pendingCount,
        float $elapsedSeconds,
        ?WebInstallMigrationProgress $progress = null,
    ): void {
        Log::channel(self::CHANNEL)->error('迁移批次执行失败', [
            'install_id' => $installId,
            'step' => 'migrate_batch',
            'stage' => WebInstallPhase::Migrating->value,
            'migration_total' => $progress?->total,
            'migration_completed' => $progress?->completed,
            'migration_pending' => $pendingCount,
            'batch_size' => $progress?->batchSize,
            'executed_migrations' => $attemptedMigrationNames,
            'elapsed_ms' => (int) round($elapsedSeconds * 1000),
            'exception_class' => $exception::class,
            'exception_message' => $exception->getMessage(),
            'occurred_at' => now()->toIso8601String(),
        ]);

        Log::channel(self::CHANNEL)->error('迁移批次异常堆栈（仅供技术人员排查）', [
            'install_id' => $installId,
            'step' => 'migrate_batch',
            'trace' => $exception->getTraceAsString(),
        ]);
    }

    private function truncateOutput(string $output): string
    {
        $output = trim($output);
        if ($output === '') {
            return '';
        }

        return mb_substr($output, 0, 4000);
    }

    /**
     * @param  array<string, mixed>  $context
     * @return array<string, mixed>
     */
    private function sanitizeContext(array $context): array
    {
        $blockedKeyFragments = ['password', 'secret', 'token', 'authorization', 'cookie', 'credential'];

        foreach ($context as $key => $_) {
            $lower = strtolower((string) $key);
            foreach ($blockedKeyFragments as $fragment) {
                if (str_contains($lower, $fragment)) {
                    $context[$key] = '[redacted]';

                    break;
                }
            }
        }

        return $context;
    }
}
