#!/usr/bin/env php
<?php

declare(strict_types=1);

require dirname(__DIR__) . '/app/bootstrap.php';

use MPS\Connectors\AudioMuseClient;
use MPS\Connectors\JellyfinClient;
use MPS\Connectors\JellystatClient;
use MPS\HttpClient;
use MPS\Mix\TemplateRegistry\TemplateMatchService;
use MPS\Mix\TemplateRegistry\TemplateRegistry;
use MPS\Mix\UserMap\UserMapService;
use MPS\Mix\Sonic\SonicCalibrationService;
use MPS\Services\DiagnosticsService;
use MPS\Services\AutomationModeService;
use MPS\Services\AutomationPortfolioService;
use MPS\Services\WorkerControlService;
use MPS\Services\AutomationService;
use MPS\Services\AdaptivePulseService;
use MPS\Services\GlobalChartService;
use MPS\Services\PlaylistPublishService;
use MPS\Services\HealthService;
use MPS\Services\HistoryService;
use MPS\Services\InventoryService;
use MPS\Services\AudioMuseCatalogService;
use MPS\Services\MixPreviewService;
use MPS\Services\MixSchedulerService;
use MPS\Services\PlaylistLifecycleService;
use MPS\Services\PlaylistClassifier;
use MPS\Services\ProfileService;
use MPS\Services\QueueService;
use MPS\Services\Worker;

$http = new HttpClient();
$jellyfin = new JellyfinClient($http);
$jellystat = new JellystatClient($http);
$audioMuse = new AudioMuseClient($http);
$classifier = new PlaylistClassifier($db);
$health = new HealthService($db, $jellyfin, $jellystat, $audioMuse);
$inventory = new InventoryService($db, $jellyfin, $classifier);
$history = new HistoryService($db, $jellystat, $jellyfin);
$profiles = new ProfileService($db);
$workerControl = new WorkerControlService($db);
$queue = new QueueService($db);
$audioMuseCatalog = new AudioMuseCatalogService($db, $audioMuse);
$sonicCalibration = new SonicCalibrationService($db);
$templateRegistry = new TemplateRegistry($db, MPS_ROOT);
$userMaps = new UserMapService($db);
$templateMatches = new TemplateMatchService($db, $templateRegistry, $userMaps);
$mixPreviews = new MixPreviewService($db, $audioMuse, $jellyfin, null, null, $templateRegistry, $userMaps);
$mixScheduler = new MixSchedulerService($db, $queue);
$automationMode = new AutomationModeService($db);
$portfolio = new AutomationPortfolioService($db);
$lifecycle = new PlaylistLifecycleService($db, $mixScheduler, $automationMode, $portfolio, $queue);
$publisher = new PlaylistPublishService($db, $jellyfin, $automationMode, $portfolio);
$automation = new AutomationService($db, $queue, $mixScheduler, $portfolio, $lifecycle, $publisher, $automationMode);
$charts = new GlobalChartService($db, $jellyfin);
$adaptivePulse = new AdaptivePulseService($db, $jellystat, $queue, $automationMode, $workerControl);
$worker = new Worker($db, $queue, $inventory, $history, $profiles, $audioMuseCatalog, $mixPreviews, $mixScheduler, $templateMatches, $portfolio, $health, $jellyfin, $automationMode, $automation, $charts, $workerControl);
$diagnostics = new DiagnosticsService($db, $audioMuse);

$command = $argv[1] ?? 'help';

try {
    switch ($command) {
        case 'help':
            echo <<<TXT
Murat Playlist Studio

Kullanım:
  php bin/console migrate              Veritabanını oluştur/güncelle
  php bin/console health               Servisleri doğrudan denetle
  php bin/console sync                 Envanter + geçmiş + AudioMuse kataloğu + profilleri eşitle
  php bin/console audiomuse:sync       Yalnızca AudioMuse kataloğunu eşitle
  php bin/console sonic:calibrate      Tam katalog için sonik dağılımı yeniden hesapla
  php bin/console sonic:status         Etkin sonik kalibrasyon özetini göster
  php bin/console templates:sync       42 disk modülünü doğrula ve kayıt defterine eşitle
  php bin/console templates:match USER Kullanıcı haritası ve 42 uyum puanını hesapla
  php bin/console templates:match-all  Tüm kullanıcıların uyum puanlarını hesapla
  php bin/console anchors:add TEMPLATE ITEM_ID [AĞIRLIK]  Güvenilir AudioMuse sonik anchor ekle
  php bin/console anchors:remove TEMPLATE ITEM_ID         Sonik anchor kaldır
  php bin/console anchors:list [TEMPLATE]                 Sonik anchorları listele
  php bin/console preview USER TEMPLATE [ADET]  Tek kullanıcı için mix önizlemesi üret
  php bin/console enqueue:preview USER TEMPLATE [ADET] Önizlemeyi kuyruğa ekle
  php bin/console enqueue:due          Süresi gelen etkin şablonları kuyruğa ekle
  php bin/console lifecycle:status     Yönetilen playlist yaşam döngüsü durumunu göster
  php bin/console lifecycle:evaluate [--enqueue] Tüketim/pasiflik eşiklerini değerlendir
  php bin/console automation:status    Otomatik/manüel sistem durumunu göster
  php bin/console worker:status        Global worker ON/OFF durumunu ve periyotları göster
  php bin/console worker:on            Worker motorunu aç; işleri periyotlarında bekle
  php bin/console worker:off           Worker motorunu kapat; bekleyen işleri kapat
  php bin/console portfolio:rebuild    42 adaydan kullanıcı bazlı dinamik portföyleri hesapla
  php bin/console automation:tick      Otomatik zinciri kuyruğa ekle; manuel modda işlem yapma
  php bin/console enqueue:curator [USER] Aktif küratör/refill/heal turunu öncelikli kuyruğa ekle
  php bin/console enqueue:consumption  Canlı Jellystat tüketim taramasını öncelikli kuyruğa ekle
  php bin/console adaptive:pulse       Hafif Jellystat değişiklik probu; yalnız gerektiğinde worker uyandırır
  php bin/console charts:refresh        Genel Top 100 + Haftalık Top 20 listelerini şimdi yenile
  php bin/console enqueue:charts        Global chart yenilemesini kuyruğa ekle
  php bin/console enqueue:health       Sağlık kontrolünü kuyruğa ekle
  php bin/console enqueue:sync         Envanter eşitlemesini kuyruğa ekle
  php bin/console queue:recover        Durdurulmuş worker görevlerini güvenle geri kazan
  php bin/console queue:cleanup        Biten görev geçmişini güvenli saklama politikasına göre buda
  php bin/console worker --once        Tek görev çalıştır
  php bin/console worker               Sürekli genel worker
  php bin/console worker:curator       Portföy/refill/profil kararlarını sürekli işle
  php bin/console worker:consumption   Yalnız canlı tüketim/temizlik görevlerini sürekli işle
  php bin/console diagnostics          Gizli anahtarsız JSON tanı raporu

TXT;
            break;

        case 'migrate':
            $templateSync = $templateRegistry->sync();
            if ($templateSync['failed'] > 0 || $templateSync['synced'] !== 42) {
                throw new RuntimeException('42 şablonun tamamı doğrulanamadı: ' . implode(' | ', $templateSync['errors']));
            }
            echo json_encode(
                ['schema' => 19, 'templates' => $templateSync],
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'templates:sync':
            $templateSync = $templateRegistry->sync();
            if ($templateSync['failed'] > 0 || $templateSync['synced'] !== 42) {
                throw new RuntimeException('42 şablonun tamamı doğrulanamadı: ' . implode(' | ', $templateSync['errors']));
            }
            echo json_encode($templateSync, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'templates:match':
            echo json_encode(
                $templateMatches->evaluateUser((string) ($argv[2] ?? ''), true),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'templates:match-all':
            echo json_encode($templateMatches->evaluateAll(true), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'anchors:add':
            $templateKey = preg_replace('/[^a-z0-9-]/', '', (string) ($argv[2] ?? '')) ?: '';
            $itemId = preg_replace('/[^a-fA-F0-9-]/', '', (string) ($argv[3] ?? '')) ?: '';
            $weight = max(0.1, min(10.0, (float) ($argv[4] ?? 1.0)));
            if ($templateKey === '' || $itemId === '') {
                throw new RuntimeException('Kullanım: anchors:add TEMPLATE ITEM_ID [AĞIRLIK]');
            }
            if ($db->one('SELECT template_key FROM managed_playlist_templates WHERE template_key = :key', [':key' => $templateKey]) === null) {
                throw new RuntimeException('Şablon bulunamadı: ' . $templateKey);
            }
            if ($db->one('SELECT jellyfin_item_id FROM audiomuse_tracks WHERE jellyfin_item_id = :id AND missing_since IS NULL', [':id' => $itemId]) === null) {
                throw new RuntimeException('Etkin AudioMuse parçası bulunamadı: ' . $itemId);
            }
            $db->pdo()->prepare(
                'INSERT INTO sonic_template_anchors (template_key, jellyfin_item_id, weight, created_at)
                 VALUES (:template, :item, :weight, :now)
                 ON CONFLICT(template_key, jellyfin_item_id) DO UPDATE SET weight = excluded.weight'
            )->execute([':template' => $templateKey, ':item' => $itemId, ':weight' => $weight, ':now' => gmdate('c')]);
            echo "Sonik anchor kaydedildi: {$templateKey} / {$itemId} / {$weight}\n";
            break;

        case 'anchors:remove':
            $templateKey = preg_replace('/[^a-z0-9-]/', '', (string) ($argv[2] ?? '')) ?: '';
            $itemId = preg_replace('/[^a-fA-F0-9-]/', '', (string) ($argv[3] ?? '')) ?: '';
            $statement = $db->pdo()->prepare(
                'DELETE FROM sonic_template_anchors WHERE template_key = :template AND jellyfin_item_id = :item'
            );
            $statement->execute([':template' => $templateKey, ':item' => $itemId]);
            echo 'Kaldırılan anchor: ' . $statement->rowCount() . "\n";
            break;

        case 'anchors:list':
            $templateKey = preg_replace('/[^a-z0-9-]/', '', (string) ($argv[2] ?? '')) ?: '';
            $sql = 'SELECT x.template_key, x.jellyfin_item_id, x.weight, x.note,
                           a.title, a.artist, a.mood_vector, a.other_features, a.energy, a.tempo
                    FROM sonic_template_anchors x
                    JOIN audiomuse_tracks a ON a.jellyfin_item_id = x.jellyfin_item_id';
            $params = [];
            if ($templateKey !== '') {
                $sql .= ' WHERE x.template_key = :template';
                $params[':template'] = $templateKey;
            }
            $sql .= ' ORDER BY x.template_key, x.weight DESC, x.jellyfin_item_id';
            echo json_encode($db->all($sql, $params), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'health':
            echo json_encode($health->checkAll(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'sync':
            $healthResult = $health->checkAll();
            if (($healthResult['jellyfin']['status'] ?? '') !== 'ok') {
                throw new RuntimeException('Jellyfin sağlık kontrolü başarısız. Mevcut envanter korunuyor.');
            }
            if (($healthResult['audiomuse']['status'] ?? '') !== 'ok') {
                throw new RuntimeException('AudioMuse sağlık kontrolü başarısız. Mevcut katalog korunuyor.');
            }
            $inventoryResult = $inventory->sync();
            $historyResult = $history->sync((string) $inventoryResult['run_id']);
            $result = [
                'inventory' => $inventoryResult,
                'history' => $historyResult,
                'audiomuse_catalog' => $audioMuseCatalog->syncIfStale(24),
                'profiles' => $profiles->rebuildAll(),
                'template_matches' => $templateMatches->evaluateAll(false),
                'automation_portfolio' => $portfolio->rebuildAll(),
                'health' => $healthResult,
            ];
            echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'audiomuse:sync':
            echo json_encode($audioMuseCatalog->sync(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'sonic:calibrate':
            echo json_encode($sonicCalibration->rebuild(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'sonic:status':
            $calibration = $sonicCalibration->current();
            echo json_encode([
                'ready' => $calibration !== null,
                'version' => $calibration['version'] ?? null,
                'catalog_run_id' => $calibration['catalog_run_id'] ?? null,
                'track_count' => $calibration['track_count'] ?? 0,
                'analyzed_track_count' => $calibration['analyzed_track_count'] ?? 0,
                'coverage' => $calibration['coverage'] ?? 0,
                'feature_count' => $calibration['feature_count'] ?? 0,
                'informative_feature_count' => $calibration['informative_feature_count'] ?? 0,
                'created_at' => $calibration['created_at'] ?? null,
            ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
            break;

        case 'preview':
            $userId = (string) ($argv[2] ?? '');
            $templateKey = (string) ($argv[3] ?? 'daily-flow');
            $trackCount = (int) ($argv[4] ?? 40);
            echo json_encode(
                $mixPreviews->generate($userId, $templateKey, $trackCount, 'manual'),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'enqueue:preview':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; görev kuyruğa alınmadı.\n";
                break;
            }
            $userId = (string) ($argv[2] ?? '');
            $templateKey = (string) ($argv[3] ?? 'daily-flow');
            $trackCount = (int) ($argv[4] ?? 40);
            echo 'Görev #' . $mixScheduler->enqueueUser($userId, $templateKey, $trackCount) . " kuyruğa alındı.\n";
            break;

        case 'enqueue:due':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; görev kuyruğa alınmadı.\n";
                break;
            }
            echo json_encode(
                $mixScheduler->enqueueDue(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'lifecycle:status':
            echo json_encode(
                $lifecycle->overview(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'lifecycle:evaluate':
            $enqueueRequested = in_array('--enqueue', $argv, true);
            $enqueueEffective = $enqueueRequested;
            $result = $lifecycle->evaluateAll($enqueueEffective);
            $result['enqueue_requested'] = $enqueueRequested;
            $result['enqueue_effective'] = $enqueueEffective;
            echo json_encode(
                $result,
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'worker:status':
            echo json_encode(
                $workerControl->summary(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'worker:on':
            echo json_encode(
                $workerControl->setMode(WorkerControlService::ON, null),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'worker:off':
            echo json_encode(
                $workerControl->setMode(WorkerControlService::OFF, null),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'automation:status':
            echo json_encode(
                $automationMode->summary(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'portfolio:rebuild':
            echo json_encode(
                $portfolio->rebuildAll(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'automation:tick':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; zamanlanmış görev üretilmedi.\n";
                break;
            }
            if (!$automationMode->isAutomatic()) {
                echo json_encode([
                    'status' => 'skipped_manual',
                    'message' => 'Sistem manuel modda; otomatik görev oluşturulmadı.',
                ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
                break;
            }
            echo 'Görev #' . $queue->enqueue(
                'automation.tick',
                ['automation' => true, 'trigger' => 'timer'],
                'automation.tick',
                50
            ) . " kuyruğa alındı.\n";
            break;

        case 'enqueue:curator':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; zamanlanmış görev üretilmedi.\n";
                break;
            }
            if (!$automationMode->isAutomatic()) {
                echo "Sistem manuel modda; aktif küratör kuyruğa alınmadı.\n";
                break;
            }
            $curatorUser = trim((string) ($argv[2] ?? ''));
            $curatorKey = $curatorUser !== '' ? 'curator.tick.user.' . $curatorUser : 'curator.tick';
            echo 'Görev #' . $queue->enqueue(
                'curator.tick',
                ['automation' => true, 'trigger' => 'curator_timer', 'user_id' => $curatorUser],
                $curatorKey,
                32
            ) . " aktif küratör kuyruğuna alındı.\n";
            break;

        case 'enqueue:consumption':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; zamanlanmış görev üretilmedi.\n";
                break;
            }
            if (!$automationMode->isAutomatic()) {
                echo "Sistem manuel modda; canlı tüketim taraması kuyruğa alınmadı.
";
                break;
            }
            echo 'Görev #' . $queue->enqueue(
                'consumption.live_tick',
                ['automation' => true, 'trigger' => 'live_timer'],
                'consumption.live_tick',
                1
            ) . " öncelikli canlı tüketim kuyruğuna alındı.
";
            break;

        case 'adaptive:pulse':
            echo json_encode(
                $adaptivePulse->pulse(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'charts:refresh':
            echo json_encode(
                $charts->refreshAll(),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'enqueue:charts':
            $jobId = $queue->enqueue(
                'charts.refresh',
                ['automation' => true, 'trigger' => 'manual_enqueue'],
                'charts.refresh',
                70,
                null
            );
            echo json_encode(['job_id' => $jobId], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
            break;

        case 'enqueue:health':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; görev kuyruğa alınmadı.\n";
                break;
            }
            echo 'Görev #' . $queue->enqueue('health.check', [], 'health.check', 10) . " kuyruğa alındı.\n";
            break;

        case 'enqueue:sync':
            if (!$workerControl->isEnabled()) {
                echo "WORKER OFF; zamanlanmış görev üretilmedi.\n";
                break;
            }
            if (!$automationMode->isAutomatic()) {
                echo "Sistem manuel modda; zamanlanmış eşitleme kuyruğa alınmadı.\n";
                break;
            }
            echo 'Görev #' . $queue->enqueue(
                'inventory.sync',
                ['automation' => true, 'trigger' => 'timer'],
                'inventory.sync',
                20
            ) . " kuyruğa alındı.\n";
            break;

        case 'queue:recover':
            echo json_encode(
                $queue->recoverInterrupted('Kurulum/güncelleme sırasında worker durduruldu'),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'queue:cleanup':
            echo json_encode(
                $queue->cleanupHistory(true),
                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            ) . "\n";
            break;

        case 'worker':
            $once = in_array('--once', $argv, true);
            $workerId = gethostname() . ':' . getmypid();
            // Genel worker ağır/genel işleri alır; tüketim ve kullanıcı-küratör
            // işleri kendi uzman workerlarında kalır. Böylece üç worker aynı
            // yüksek öncelikli işi kapmak için SQLite kuyruğunu dövmez.
            $allowedTypes = [
                'health.check',
                'inventory.sync',
                'audiomuse.catalog_sync',
                'profile.rebuild_all',
                'templates.match_all',
                'mix.preview',
                'charts.refresh',
                'mix.enqueue_due',
                'automation.tick',
                'playlist.publish_auto',
                'playlist.delete_legacy',
            ];
            $idleSleep = 1;
            do {
                if (!$workerControl->isEnabled()) {
                    if ($once) {
                        break;
                    }
                    sleep(5);
                    continue;
                }
                $worked = $worker->runOnce($workerId, $allowedTypes);
                if ($once) {
                    break;
                }
                if ($worked) {
                    $idleSleep = 1;
                } else {
                    sleep($idleSleep);
                    $idleSleep = min(12, $idleSleep * 2);
                }
            } while (true);
            break;

        case 'worker:curator':
            $workerId = gethostname() . ':curator:' . getmypid();
            $allowedTypes = [
                'curator.tick',
                'profile.rebuild',
                'templates.match_user',
            ];
            $idleSleep = 1;
            do {
                if (!$workerControl->isEnabled()) {
                    sleep(5);
                    continue;
                }
                $worked = $worker->runOnce($workerId, $allowedTypes, 10);
                if ($worked) {
                    $idleSleep = 1;
                } else {
                    sleep($idleSleep);
                    $idleSleep = min(10, $idleSleep * 2);
                }
            } while (true);
            break;

        case 'worker:consumption':
            $workerId = gethostname() . ':consumption:' . getmypid();
            $allowedTypes = [
                'consumption.live_tick',
                'consumption.sweep_local',
                'playlist.prune_consumed',
                'playlist.retire_auto',
            ];
            $idleSleep = 1;
            do {
                if (!$workerControl->isEnabled()) {
                    sleep(5);
                    continue;
                }
                $worked = $worker->runOnce($workerId, $allowedTypes, 5);
                if ($worked) {
                    $idleSleep = 1;
                } else {
                    sleep($idleSleep);
                    $idleSleep = min(6, $idleSleep * 2);
                }
            } while (true);
            break;

        case 'diagnostics':
            echo json_encode($diagnostics->export(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
            break;

        default:
            fwrite(STDERR, "Bilinmeyen komut: {$command}\n");
            exit(2);
    }
} catch (Throwable $error) {
    fwrite(STDERR, 'HATA: ' . $error->getMessage() . "\n");
    exit(1);
}
