<?php

declare(strict_types=1);

namespace App\Http\Controllers\Admin;

use App\Domain\Account\Models\Account;
use App\Domain\Cluster\Models\ClusterHeartbeat;
use App\Domain\Cluster\Models\ClusterJob;
use App\Domain\Cluster\Models\ClusterNode;
use App\Domain\Cluster\Services\ClusterManager;
use App\Domain\Cluster\Services\ClusterOrchestrator;
use App\Domain\Sysapi\Exceptions\SysapiException;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

/**
 * Admin → Cluster Yönetimi (Multi-Server topology).
 *
 * Orkestrasyon iki katmandan oluşur:
 *   - ClusterOrchestrator: heartbeat, job kuyruğu, secret, install komutu
 *   - ClusterManager: sysapi-aware exec/sync/ping/promote/replication kısayolları
 */
class ClusterController extends Controller
{
    public function __construct(
        private readonly ClusterOrchestrator $orchestrator,
        private readonly ClusterManager $manager,
    ) {}

    // ── Index ─────────────────────────────────────────────────────────────────

    public function index(Request $request): Response
    {
        $nodes = ClusterNode::whereNull('deleted_at')
            ->latest()
            ->get()
            ->map(fn (ClusterNode $n) => $this->mapNode($n));

        return Inertia::render('Admin/Cluster/Index', [
            'nodes' => $nodes,
            'stats' => [
                'total' => $nodes->count(),
                'active' => $nodes->where('status', 'active')->count(),
                'down' => $nodes->where('status', 'down')->count(),
                'total_capacity' => $nodes->sum('max_account_count'),
                'total_used' => $nodes->sum('current_account_count'),
            ],
        ]);
    }

    // ── Show (tek node detay) ─────────────────────────────────────────────────

    public function show(ClusterNode $node): Response
    {
        $jobs = ClusterJob::where('cluster_node_id', $node->id)
            ->orderByDesc('created_at')
            ->limit(50)
            ->get()
            ->map(fn (ClusterJob $j) => [
                'id' => $j->id,
                'action' => $j->action,
                'status' => $j->status,
                'attempts' => $j->attempts,
                'duration_ms' => $j->duration_ms,
                'error' => $j->error,
                'created_at' => $j->created_at->format('d.m.Y H:i:s'),
                'finished_at' => $j->finished_at?->format('d.m.Y H:i:s'),
            ]);

        // Son 24 saat heartbeat (saat başı gruplu, CSS chart için)
        $heartbeats = ClusterHeartbeat::where('cluster_node_id', $node->id)
            ->where('received_at', '>=', now()->subHours(24))
            ->orderBy('received_at')
            ->get()
            ->map(fn (ClusterHeartbeat $h) => [
                'time' => $h->received_at->format('H:i'),
                'cpu_pct' => $h->load['cpu_pct'] ?? 0,
                'mem_pct' => $h->load['mem_pct'] ?? 0,
                'disk_pct' => $h->load['disk_pct'] ?? 0,
                'accounts_count' => $h->accounts_count,
            ]);

        // Cluster içindeki diğer node'lar — manual exec + migration UI için
        $otherNodes = ClusterNode::whereNull('deleted_at')
            ->where('id', '<>', $node->id)
            ->get(['id', 'name', 'hostname', 'role', 'status']);

        // Mevcut heartbeat'in zenginleştirilmiş payload'ından services/jobs/version
        $latestHeartbeat = ClusterHeartbeat::where('cluster_node_id', $node->id)
            ->latest('received_at')
            ->first();
        $latestLoad = $latestHeartbeat?->load ?? [];

        return Inertia::render('Admin/Cluster/Show', [
            'node' => $this->mapNode($node),
            'jobs' => $jobs,
            'heartbeats' => $heartbeats,
            'other_nodes' => $otherNodes,
            'extras' => [
                'services' => $latestLoad['services'] ?? null,
                'network' => $latestLoad['network'] ?? null,
                'cpu_load_avg' => $latestLoad['cpu_load'] ?? null,
                'active_jobs_reported' => $latestLoad['active_jobs'] ?? null,
                'agent_version' => $latestLoad['version'] ?? null,
            ],
        ]);
    }

    // ── Jobs listesi (AJAX/JSON) ───────────────────────────────────────────────

    public function jobs(ClusterNode $node): JsonResponse
    {
        $jobs = $this->orchestrator
            ->listJobsForNode($node)
            ->map(fn (ClusterJob $j) => [
                'id' => $j->id,
                'action' => $j->action,
                'status' => $j->status,
                'duration_ms' => $j->duration_ms,
                'created_at' => $j->created_at->diffForHumans(),
            ]);

        return response()->json(['jobs' => $jobs]);
    }

    // ── Secret yönetimi ───────────────────────────────────────────────────────

    public function regenerateSecret(ClusterNode $node): JsonResponse
    {
        $secret = $this->orchestrator->regenerateSecret($node);
        $installCmd = $this->orchestrator->buildInstallCommand($node, $secret);

        return response()->json([
            'secret' => $secret,
            'install_cmd' => $installCmd,
        ]);
    }

    public function installScript(ClusterNode $node): JsonResponse
    {
        if (! $node->api_secret) {
            return response()->json(['error' => 'Önce secret üret.'], 400);
        }

        $installCmd = $this->orchestrator->buildInstallCommand($node, '****MEVCUT-SECRET****');

        return response()->json([
            'install_cmd' => $installCmd,
            'node_id' => $node->id,
            'note' => 'Gerçek secret için "Secret Yenile" butonunu kullan.',
        ]);
    }

    // ── Job actions ───────────────────────────────────────────────────────────

    public function cancelJob(ClusterJob $job): RedirectResponse
    {
        try {
            $this->orchestrator->cancelJob($job);

            return back()->with('success', "Job #{$job->id} iptal edildi.");
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }
    }

    public function retryJob(ClusterJob $job): RedirectResponse
    {
        try {
            $newJob = $this->orchestrator->retryJob($job);

            return back()->with('success', "Job #{$job->id} yeniden kuyruğa alındı (yeni job: #{$newJob->id}).");
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }
    }

    // ── CRUD ─────────────────────────────────────────────────────────────────

    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'hostname' => ['required', 'string', 'max:191', 'unique:cluster_nodes,hostname'],
            'ip_address' => ['required', 'ip'],
            'role' => ['required', 'in:control_plane,web,database,dns,mail,cache,storage'],
            'environment' => ['required', 'in:production,staging,development'],
            'cpu_cores' => ['nullable', 'integer', 'min:1', 'max:128'],
            'ram_mb' => ['nullable', 'integer', 'min:512'],
            'disk_gb' => ['nullable', 'integer', 'min:10'],
            'region' => ['nullable', 'string', 'max:50'],
            'provider' => ['nullable', 'string', 'max:50'],
            'max_account_count' => ['nullable', 'integer', 'min:0'],
            'agent_url' => ['nullable', 'url', 'max:191'],
            'agent_token' => ['nullable', 'string'],
            'ssh_user' => ['nullable', 'string', 'max:100'],
            'ssh_port' => ['nullable', 'integer', 'min:1', 'max:65535'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);

        $node = ClusterNode::create(array_merge($validated, ['status' => 'provisioning']));

        // Otomatik secret üret
        $secret = $this->orchestrator->regenerateSecret($node);
        $installCmd = $this->orchestrator->buildInstallCommand($node, $secret);

        return back()
            ->with('success', "Node '{$validated['name']}' eklendi.")
            ->with('install_cmd', $installCmd)
            ->with('api_secret', $secret);
    }

    public function destroy(ClusterNode $node): RedirectResponse
    {
        if ($node->current_account_count > 0) {
            return back()->with(
                'error',
                "Bu node'da {$node->current_account_count} aktif hesap var. Önce hesapları transfer et."
            );
        }
        $node->delete();

        return back()->with('success', 'Node kaldırıldı.');
    }

    public function toggleAccept(ClusterNode $node): RedirectResponse
    {
        $node->update(['accept_new_accounts' => ! $node->accept_new_accounts]);
        $msg = $node->accept_new_accounts
            ? 'Yeni hesap kabulü açıldı.'
            : 'Yeni hesap kabulü durduruldu (mevcut hesaplar etkilenmez).';

        return back()->with('success', $msg);
    }

    // ── Cluster Manager ops (ping / sync / exec / promote) ───────────────────

    /**
     * Slave node'a HMAC-imzalı health-check isteği.
     */
    public function ping(ClusterNode $node): JsonResponse
    {
        $result = $this->manager->pingNode($node->id);

        return response()->json($result, $result['ok'] ? 200 : 502);
    }

    /**
     * Master DB state'ini slave'e sync için job kuyruğuna yaz.
     */
    public function sync(Request $request, ClusterNode $node): JsonResponse
    {
        $result = $this->manager->syncStateToNode($node->id, $request->user());

        return response()->json($result, 202);
    }

    /**
     * Admin'in slave node'da whitelisted sysapi action çalıştırması.
     */
    public function exec(Request $request, ClusterNode $node): JsonResponse
    {
        $validated = $request->validate([
            'action' => ['required', 'string', 'regex:/^[a-z][a-z0-9-]{2,30}$/'],
            'args' => ['nullable', 'array'],
        ]);

        try {
            $result = $this->manager->executeOnNode(
                $node->id,
                $validated['action'],
                $validated['args'] ?? [],
                $request->user(),
            );
        } catch (SysapiException $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        } catch (\RuntimeException $e) {
            return response()->json(['error' => $e->getMessage()], 409);
        }

        return response()->json($result, 202);
    }

    /**
     * Slave'i replication topology'sinde master rolüne yükselt.
     */
    public function promoteSlave(Request $request, ClusterNode $node): RedirectResponse
    {
        $validated = $request->validate([
            'service_type' => ['nullable', 'in:mariadb,powerdns,pdns,redis'],
        ]);

        $result = $this->manager->promoteSlave(
            $node->id,
            $validated['service_type'] ?? 'mariadb',
        );

        $msg = ($result['success'] ?? false)
            ? "Node '{$node->name}' master rolüne yükseltildi."
            : 'Promote başarısız: '.($result['error'] ?? 'bilinmeyen hata');

        return back()->with($result['success'] ?? false ? 'success' : 'error', $msg);
    }

    /**
     * MariaDB slave status — SHOW SLAVE STATUS özeti.
     */
    public function slaveStatus(ClusterNode $node): JsonResponse
    {
        $result = $this->manager->getMariaDbSlaveStatus($node->id);

        return response()->json($result);
    }

    /**
     * Topology özeti (dashboard widget).
     */
    public function summary(): JsonResponse
    {
        return response()->json($this->manager->topologySummary());
    }

    // ── Real-time health (UI auto-refresh 10sn) ──────────────────────────────

    /**
     * Cluster genelinde real-time health snapshot.
     * UI polling endpoint — Admin/Cluster/Index.vue 10 saniyede bir çağırır.
     */
    public function health(): JsonResponse
    {
        return response()->json($this->manager->realtimeHealth());
    }

    // ── Cluster sync wizard ──────────────────────────────────────────────────

    public function syncWizard(): Response
    {
        $nodes = ClusterNode::whereNull('deleted_at')
            ->orderBy('id')
            ->get()
            ->map(fn (ClusterNode $n) => [
                'id' => $n->id,
                'name' => $n->name,
                'hostname' => $n->hostname,
                'ip_address' => $n->ip_address,
                'role' => $n->role,
                'status' => $n->status,
                'is_healthy' => $n->isHealthy(),
                'cpu_pct' => $n->cpuPct(),
                'mem_pct' => $n->memPct(),
                'disk_pct' => $n->diskPct(),
                'accounts' => $n->current_account_count,
                'max_accounts' => $n->max_account_count,
                'accept_new_accounts' => $n->accept_new_accounts,
            ])
            ->values();

        $accounts = Account::orderBy('username')
            ->limit(500)
            ->get(['id', 'username', 'primary_domain', 'email'])
            ->map(fn ($a) => [
                'id' => $a->id,
                'username' => $a->username,
                'primary_domain' => $a->primary_domain,
                'email' => $a->email,
            ]);

        return Inertia::render('Admin/Cluster/Sync', [
            'nodes' => $nodes,
            'accounts' => $accounts,
        ]);
    }

    // ── Cross-node account migration ─────────────────────────────────────────

    /**
     * Migration için pre-flight: disk + agent + kapasite + DNS uyumluluk.
     */
    public function migratePreflight(Request $request, ClusterNode $node): JsonResponse
    {
        $validated = $request->validate([
            'account_id' => ['required', 'integer', 'exists:accounts,id'],
        ]);

        try {
            $result = $this->manager->preflightMigration($validated['account_id'], $node->id);
        } catch (\RuntimeException $e) {
            return response()->json(['error' => $e->getMessage()], 409);
        }

        return response()->json($result);
    }

    /**
     * Hesabı target node'a taşı (snapshot + rsync + restore + DNS update).
     */
    public function migrateAccount(Request $request, ClusterNode $node, Account $account): JsonResponse
    {
        try {
            $job = $this->manager->migrateAccount($account->id, $node->id, $request->user());
        } catch (\RuntimeException $e) {
            return response()->json(['error' => $e->getMessage()], 409);
        }

        return response()->json([
            'job_id' => $job->id,
            'status' => $job->status,
            'target_node' => $node->hostname,
            'account' => $account->username,
            'note' => 'Migrasyon kuyruğa alındı; slave-agent\'ın bir sonraki heartbeat\'inde çekilir.',
        ], 202);
    }

    // ── Agent token rotation ──────────────────────────────────────────────────

    /**
     * Node için yeni HMAC api_secret üret ve cluster-token-rotate job ile push.
     * Eski secret 60sn grace period sonra geçersiz olur.
     */
    public function rotateToken(Request $request, ClusterNode $node): JsonResponse
    {
        try {
            $result = $this->manager->rotateAgentToken($node->id, $request->user());
        } catch (\RuntimeException $e) {
            return response()->json(['error' => $e->getMessage()], 409);
        }

        return response()->json([
            'secret' => $result['secret'],
            'job_id' => $result['job_id'],
            'fingerprint' => substr(hash('sha256', $result['secret']), 0, 16),
            'note' => $result['note'],
        ]);
    }

    // ── Helper ───────────────────────────────────────────────────────────────

    private function mapNode(ClusterNode $n): array
    {
        return [
            'id' => $n->id,
            'name' => $n->name,
            'hostname' => $n->hostname,
            'ip_address' => $n->ip_address,
            'role' => $n->role,
            'environment' => $n->environment,
            'cpu_cores' => $n->cpu_cores,
            'ram_mb' => $n->ram_mb,
            'disk_gb' => $n->disk_gb,
            'region' => $n->region,
            'provider' => $n->provider,
            'status' => $n->status,
            'is_healthy' => $n->isHealthy(),
            'last_heartbeat_at' => $n->last_heartbeat_at?->diffForHumans(),
            'current_account_count' => $n->current_account_count,
            'max_account_count' => $n->max_account_count,
            'accept_new_accounts' => $n->accept_new_accounts,
            'software_versions' => $n->software_versions,
            'tags' => $n->tags,
            'current_load' => $n->current_load,
            'capacity' => $n->capacity,
            'os_version' => $n->os_version,
            'panel_version' => $n->panel_version,
            'has_secret' => ! empty($n->api_secret),
            'pending_jobs' => $n->pendingJobsCount(),
            'created_at' => $n->created_at->format('d.m.Y'),
        ];
    }
}
