<?php

namespace App\Services\Stats;

use App\Models\Stats\StatJobLog;
use App\Models\Tenants\Tenant;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

class StatAggregatorService
{
    /**
     * 일간 도메인 서비스 매핑
     */
    private function getDailyDomainServices(): array
    {
        return [
            'sales' => SalesStatService::class,
            'finance' => FinanceStatService::class,
            'production' => ProductionStatService::class,
            'inventory' => InventoryStatService::class,
            'quote' => QuoteStatService::class,
            'hr' => HrStatService::class,
            'system' => SystemStatService::class,
        ];
    }

    /**
     * 월간 도메인 서비스 매핑
     */
    private function getMonthlyDomainServices(): array
    {
        return [
            'sales' => SalesStatService::class,
            'finance' => FinanceStatService::class,
            'production' => ProductionStatService::class,
            'inventory' => InventoryStatService::class,
            'quote' => QuoteStatService::class,
            'hr' => HrStatService::class,
            'project' => ProjectStatService::class,
        ];
    }

    /**
     * 일간 통계 집계
     */
    public function aggregateDaily(Carbon $date, ?string $domain = null, ?int $tenantId = null): array
    {
        $startTime = microtime(true);
        $errors = [];
        $tenantsProcessed = 0;
        $domainsProcessed = 0;

        $tenants = $this->getTargetTenants($tenantId);
        $services = $this->getDailyDomainServices();

        if ($domain) {
            $services = array_intersect_key($services, [$domain => true]);
        }

        foreach ($tenants as $tenant) {
            foreach ($services as $domainKey => $serviceClass) {
                $jobLog = $this->createJobLog($tenant->id, "{$domainKey}_daily", $date);

                try {
                    $jobLog->markRunning();

                    /** @var StatDomainServiceInterface $service */
                    $service = app($serviceClass);
                    $recordCount = $service->aggregateDaily($tenant->id, $date);

                    $jobLog->markCompleted($recordCount);
                    $domainsProcessed++;

                    // 캐시 무효화
                    StatQueryService::invalidateCache($tenant->id, $domainKey);
                } catch (\Throwable $e) {
                    $errorMsg = "[{$domainKey}] tenant={$tenant->id}: {$e->getMessage()}";
                    $errors[] = $errorMsg;
                    $jobLog->markFailed($e->getMessage());

                    // 모니터링 알림 기록
                    app(StatMonitorService::class)->recordAggregationFailure(
                        $tenant->id, $domainKey, "{$domainKey}_daily", $e->getMessage()
                    );

                    Log::error('stat:aggregate-daily 실패', [
                        'domain' => $domainKey,
                        'tenant_id' => $tenant->id,
                        'date' => $date->format('Y-m-d'),
                        'error' => $e->getMessage(),
                    ]);
                }
            }
            $tenantsProcessed++;
        }

        $durationMs = (int) ((microtime(true) - $startTime) * 1000);

        return [
            'tenants_processed' => $tenantsProcessed,
            'domains_processed' => $domainsProcessed,
            'errors' => $errors,
            'duration_ms' => $durationMs,
        ];
    }

    /**
     * 월간 통계 집계
     */
    public function aggregateMonthly(int $year, int $month, ?string $domain = null, ?int $tenantId = null): array
    {
        $startTime = microtime(true);
        $errors = [];
        $tenantsProcessed = 0;
        $domainsProcessed = 0;

        $tenants = $this->getTargetTenants($tenantId);
        $services = $this->getMonthlyDomainServices();

        if ($domain) {
            $services = array_intersect_key($services, [$domain => true]);
        }

        $targetDate = Carbon::create($year, $month, 1);

        foreach ($tenants as $tenant) {
            foreach ($services as $domainKey => $serviceClass) {
                $jobLog = $this->createJobLog($tenant->id, "{$domainKey}_monthly", $targetDate);

                try {
                    $jobLog->markRunning();

                    /** @var StatDomainServiceInterface $service */
                    $service = app($serviceClass);
                    $recordCount = $service->aggregateMonthly($tenant->id, $year, $month);

                    $jobLog->markCompleted($recordCount);
                    $domainsProcessed++;

                    // 캐시 무효화
                    StatQueryService::invalidateCache($tenant->id, $domainKey);
                } catch (\Throwable $e) {
                    $errorMsg = "[{$domainKey}] tenant={$tenant->id}: {$e->getMessage()}";
                    $errors[] = $errorMsg;
                    $jobLog->markFailed($e->getMessage());

                    // 모니터링 알림 기록
                    app(StatMonitorService::class)->recordAggregationFailure(
                        $tenant->id, $domainKey, "{$domainKey}_monthly", $e->getMessage()
                    );

                    Log::error('stat:aggregate-monthly 실패', [
                        'domain' => $domainKey,
                        'tenant_id' => $tenant->id,
                        'year' => $year,
                        'month' => $month,
                        'error' => $e->getMessage(),
                    ]);
                }
            }
            $tenantsProcessed++;
        }

        $durationMs = (int) ((microtime(true) - $startTime) * 1000);

        return [
            'tenants_processed' => $tenantsProcessed,
            'domains_processed' => $domainsProcessed,
            'errors' => $errors,
            'duration_ms' => $durationMs,
        ];
    }

    /**
     * 대상 테넌트 목록 조회
     */
    private function getTargetTenants(?int $tenantId): \Illuminate\Database\Eloquent\Collection
    {
        $query = Tenant::query()->where('tenant_st_code', '!=', 'none');

        if ($tenantId) {
            $query->where('id', $tenantId);
        }

        return $query->get();
    }

    /**
     * 집계 작업 로그 생성 (멱등: 동일 조건이면 기존 레코드 재사용)
     */
    private function createJobLog(int $tenantId, string $jobType, Carbon $targetDate): StatJobLog
    {
        return StatJobLog::updateOrCreate(
            [
                'tenant_id' => $tenantId,
                'job_type' => $jobType,
                'target_date' => $targetDate->format('Y-m-d'),
            ],
            [
                'status' => 'pending',
                'records_processed' => 0,
                'error_message' => null,
                'started_at' => null,
                'completed_at' => null,
                'duration_ms' => null,
                'created_at' => now(),
            ]
        );
    }
}
