<?php

declare(strict_types=1);

namespace App\Domain\Account\Services;

use App\Domain\Account\Enums\AccountStatus;
use App\Domain\Account\Events\AccountProvisioned;
use App\Domain\Account\Events\AccountSuspended;
use App\Domain\Account\Events\AccountTerminated;
use App\Domain\Account\Events\AccountUnsuspended;
use App\Domain\Account\Exceptions\AccountException;
use App\Domain\Account\Models\Account;
use App\Domain\Sysapi\Contracts\SysapiBridge;
use Illuminate\Support\Facades\DB;

/**
 * Account lifecycle yönetimi (sysapi köprüsü üzerinden).
 *
 * Sorumluluklar:
 *   - Status transition validation
 *   - Sysapi script çağrısı (user-add, user-suspend, vs.)
 *   - DB state update (atomic — DB transaction içinde)
 *   - Event emit (AccountProvisioned, AccountSuspended, AccountTerminated)
 *
 * Test edilebilir:
 *   $this->app->instance(SysapiBridge::class, new FakeSysapi());
 */
final class AccountProvisioner
{
    public function __construct(private readonly SysapiBridge $sysapi) {}

    /**
     * Pending hesabı provision et (Linux user oluştur).
     *
     * @throws AccountException
     */
    public function provision(Account $account): Account
    {
        if (! $account->status->canTransitionTo(AccountStatus::Active)) {
            throw AccountException::invalidTransition($account, AccountStatus::Active);
        }

        if ($account->isProvisioned()) {
            throw AccountException::alreadyProvisioned($account);
        }

        $result = $this->sysapi->run('user-add', [
            'username' => $account->username,
            'email' => $account->email,
            'quota_bytes' => $account->quota_bytes,
            'package' => $account->package?->slug ?? 'starter',
            'domain' => $account->primary_domain,
            'home_base' => config('onoxsoft.account.home_base', '/home'),
            'uid_min' => config('onoxsoft.account.uid_min', 10_000),
            'uid_max' => config('onoxsoft.account.uid_max', 65_000),
        ], timeoutSeconds: 120);

        if ($result->failed()) {
            // Provision başarısız → hesabı terminated'a al
            DB::transaction(function () use ($account) {
                $account->update([
                    'status' => AccountStatus::Terminated,
                    'terminated_at' => now(),
                ]);
            });

            throw AccountException::sysapiFailed($account, 'provision', $result);
        }

        // Başarılı — UID/GID + skeleton + status güncelle
        DB::transaction(function () use ($account, $result) {
            $account->update([
                'status' => AccountStatus::Active,
                'linux_uid' => $result->data['uid'] ?? null,
                'linux_gid' => $result->data['gid'] ?? null,
                'skeleton_version' => $result->data['skeleton_version'] ?? '1',
                'provisioned_at' => now(),
            ]);
        });

        AccountProvisioned::dispatch($account->fresh());

        return $account->refresh();
    }

    /**
     * Hesabı askıya al (vhost'ları disable, FPM stop, ama disk koru).
     *
     * @throws AccountException
     */
    public function suspend(Account $account, string $reason): Account
    {
        if (! $account->status->canTransitionTo(AccountStatus::Suspended)) {
            throw AccountException::invalidTransition($account, AccountStatus::Suspended);
        }

        $result = $this->sysapi->run('user-suspend', [
            'username' => $account->username,
            'reason' => $reason,
        ]);

        if ($result->failed()) {
            throw AccountException::sysapiFailed($account, 'suspend', $result);
        }

        DB::transaction(function () use ($account, $reason) {
            $account->update([
                'status' => AccountStatus::Suspended,
                'suspended_at' => now(),
                'suspended_reason' => $reason,
            ]);
        });

        AccountSuspended::dispatch($account->fresh(), $reason);

        return $account->refresh();
    }

    /**
     * Askıdan çıkar (vhost enable, FPM start).
     *
     * @throws AccountException
     */
    public function unsuspend(Account $account): Account
    {
        if (! $account->status->canTransitionTo(AccountStatus::Active)) {
            throw AccountException::invalidTransition($account, AccountStatus::Active);
        }

        $result = $this->sysapi->run('user-unsuspend', [
            'username' => $account->username,
        ]);

        if ($result->failed()) {
            throw AccountException::sysapiFailed($account, 'unsuspend', $result);
        }

        DB::transaction(function () use ($account) {
            $account->update([
                'status' => AccountStatus::Active,
                'suspended_at' => null,
                'suspended_reason' => null,
            ]);
        });

        AccountUnsuspended::dispatch($account->fresh());

        return $account->refresh();
    }

    /**
     * Hesabı sonlandır. $deleteData=true → /home/<user> silinir (tar.gz yedek alınır).
     * $deleteData=false → sadece Linux user disabled, data 30 gün korunur.
     *
     * @throws AccountException
     */
    public function terminate(Account $account, bool $deleteData = true, bool $backupFirst = true): Account
    {
        if (! $account->status->canTransitionTo(AccountStatus::Terminated)) {
            throw AccountException::invalidTransition($account, AccountStatus::Terminated);
        }

        $result = $this->sysapi->run('user-terminate', [
            'username' => $account->username,
            'delete_data' => $deleteData,
            'backup_first' => $backupFirst,
        ], timeoutSeconds: 300);

        if ($result->failed()) {
            throw AccountException::sysapiFailed($account, 'terminate', $result);
        }

        DB::transaction(function () use ($account) {
            $account->update([
                'status' => AccountStatus::Terminated,
                'terminated_at' => now(),
                'linux_uid' => null, // UID artık başkasına atanabilir
                'linux_gid' => null,
                'skeleton_version' => null,
            ]);
        });

        AccountTerminated::dispatch($account->fresh(), $deleteData);

        return $account->refresh();
    }

    /**
     * XFS disk kotası uygula.
     *
     * @param  int  $diskMb  Soft limit (MiB). Hard limit = diskMb * 1.10.
     *
     * @throws AccountException
     */
    public function applyQuota(Account $account, int $diskMb): Account
    {
        if (! $account->isProvisioned()) {
            throw new AccountException(
                "Kota uygulanamaz: hesap henüz provision edilmemiş ({$account->username})",
                account: $account,
            );
        }

        $result = $this->sysapi->run('quota-apply', [
            'username' => $account->username,
            'disk_mb' => $diskMb,
            'inodes' => $account->package?->limit('inodes', 100_000),
        ]);

        if ($result->failed()) {
            throw AccountException::sysapiFailed($account, 'quota-apply', $result);
        }

        $softBytes = ($result->data['soft_mb'] ?? $diskMb) * 1024 * 1024;

        DB::transaction(function () use ($account, $softBytes) {
            $account->update([
                'quota_bytes' => $softBytes,
            ]);
        });

        return $account->refresh();
    }

    /**
     * Hesabın güncel disk kullanımını sysapi'dan çek ve modeli güncelle.
     *
     * @return array{used_mb: int, soft_mb: int, hard_mb: int, files_used: int, pct: float}
     *
     * @throws AccountException
     */
    public function checkQuota(Account $account): array
    {
        if (! $account->isProvisioned()) {
            throw new AccountException(
                "Kota sorgulanamaz: hesap henüz provision edilmemiş ({$account->username})",
                account: $account,
            );
        }

        $result = $this->sysapi->run('quota-check', [
            'username' => $account->username,
        ]);

        if ($result->failed()) {
            throw AccountException::sysapiFailed($account, 'quota-check', $result);
        }

        $data = $result->data ?? [];
        $usedBytes = (int) ($data['used_mb'] ?? 0) * 1024 * 1024;

        // Disk kullanımını DB'ye yaz (best-effort — transaction dışında)
        $account->update([
            'disk_usage_bytes' => $usedBytes,
            'usage_updated_at' => now(),
        ]);

        return [
            'used_mb' => (int) ($data['used_mb'] ?? 0),
            'soft_mb' => (int) ($data['soft_mb'] ?? 0),
            'hard_mb' => (int) ($data['hard_mb'] ?? 0),
            'files_used' => (int) ($data['files_used'] ?? 0),
            'pct' => (float) ($data['pct'] ?? 0),
        ];
    }

    /**
     * Linux / FTP / WebDAV şifresi sıfırla.
     *
     * @param  string  $hashedPassword  Şifre (chpasswd için pre-hashed veya plaintext)
     * @param  string  $scope  "linux" | "ftp" | "webdisk" | "all"
     * @return array{username: string, updated: list<string>}
     *
     * @throws AccountException
     */
    public function resetPassword(Account $account, string $hashedPassword, string $scope = 'all'): array
    {
        if (! $account->isProvisioned()) {
            throw new AccountException(
                "Şifre sıfırlanamaz: hesap henüz provision edilmemiş ({$account->username})",
                account: $account,
            );
        }

        $result = $this->sysapi->run('user-password', [
            'username' => $account->username,
            'password' => $hashedPassword,
            'scope' => $scope,
        ]);

        if ($result->failed()) {
            throw AccountException::sysapiFailed($account, 'user-password', $result);
        }

        return [
            'username' => $account->username,
            'updated' => $result->data['updated'] ?? [],
        ];
    }
}
