<?php

namespace App\Services\Whmcs;

use Illuminate\Support\Facades\DB;
use Throwable;

/**
 * Single source of truth for the WHMCS connection config. Stores fields in
 * the existing `settings` table (group='whmcs', key='connection') as one
 * encrypted JSON blob — credentials never appear in plaintext at rest, in
 * logs, or in API responses.
 *
 * Falls back to env-based config (WHMCS_URL etc) when no DB row exists,
 * so existing installs keep working through the migration window.
 */
class WhmcsConnection
{
    private const GROUP = 'whmcs';
    private const KEY   = 'connection';

    /** Returns ['url', 'identifier', 'secret', 'investigation_enabled', 'ssl_verify', ...]. */
    public function get(): array
    {
        $row = DB::table('settings')
            ->where('group', self::GROUP)
            ->where('key', self::KEY)
            ->first();

        if ($row && $row->value) {
            try {
                // Column is JSON; we store the encrypted string wrapped in
                // a JSON string. Unwrap then decrypt.
                $stored = is_string($row->value) ? json_decode($row->value, true) : $row->value;
                if (is_string($stored)) $stored = decrypt($stored);
                $data = is_array($stored) ? $stored : json_decode((string) $stored, true);
                if (is_array($data) && ($data['url'] ?? null)) {
                    return $this->withMeta($data);
                }
            } catch (Throwable) {
                // Corrupted or never-encrypted value — fall through to env.
            }
        }

        return $this->withMeta($this->envFallback());
    }

    /**
     * Update the connection config. Empty string for secret/identifier means
     * "leave existing untouched" — same convention as our other masked-field
     * forms. Returns the merged-and-saved config (with secrets stripped).
     */
    public function set(array $patch): array
    {
        $current = $this->get();
        $merged = $current;

        foreach (['url', 'investigation_dept_ids'] as $f) {
            if (array_key_exists($f, $patch)) $merged[$f] = (string) $patch[$f];
        }
        foreach (['investigation_enabled', 'ssl_verify'] as $bf) {
            if (array_key_exists($bf, $patch)) $merged[$bf] = (bool) $patch[$bf];
        }
        // Secrets — only overwrite if non-empty (empty = "keep existing").
        foreach (['identifier', 'secret'] as $sf) {
            if (array_key_exists($sf, $patch) && trim((string) $patch[$sf]) !== '') {
                $merged[$sf] = (string) $patch[$sf];
            }
        }

        $this->writeRaw($merged);
        return $this->safe($merged);
    }

    public function recordTestResult(bool $ok, string $message): void
    {
        $cur = $this->get();
        $cur['last_tested_at']   = now()->toIso8601String();
        $cur['last_test_ok']     = $ok;
        $cur['last_test_message']= substr($message, 0, 500);
        $this->writeRaw($cur);
    }

    /**
     * Public-safe view (URL + flags + test status visible, secrets masked).
     * This shape is what the UI receives.
     */
    public function safe(?array $data = null): array
    {
        $d = $data ?? $this->get();
        return [
            'url'                     => $d['url'] ?? '',
            'identifier_masked'       => $this->mask($d['identifier'] ?? ''),
            'secret_masked'           => $this->mask($d['secret'] ?? ''),
            'has_identifier'          => ! empty($d['identifier']),
            'has_secret'              => ! empty($d['secret']),
            'investigation_enabled'   => (bool) ($d['investigation_enabled'] ?? false),
            'investigation_dept_ids'  => $d['investigation_dept_ids'] ?? '',
            'ssl_verify'              => (bool) ($d['ssl_verify'] ?? true),
            'last_tested_at'          => $d['last_tested_at'] ?? null,
            'last_test_ok'            => $d['last_test_ok'] ?? null,
            'last_test_message'       => $d['last_test_message'] ?? null,
            'source'                  => $d['_source'] ?? 'env',
        ];
    }

    private function writeRaw(array $data): void
    {
        unset($data['_source']);
        // Settings.value is a JSON column on NOC. Wrap our encrypted ciphertext
        // string as a JSON string so the column constraint accepts it.
        $payload = json_encode(encrypt(json_encode($data)));
        DB::table('settings')->updateOrInsert(
            ['key' => self::KEY],
            [
                'group'      => self::GROUP,
                'value'      => $payload,
                'encrypted'  => true,
                'updated_at' => now(),
            ],
        );
    }

    private function envFallback(): array
    {
        return [
            'url'                    => (string) env('WHMCS_URL', ''),
            'identifier'             => (string) env('WHMCS_API_IDENTIFIER', ''),
            'secret'                 => (string) env('WHMCS_API_SECRET', ''),
            'investigation_enabled'  => env('WHMCS_INVESTIGATION_ENABLED', false),
            'investigation_dept_ids' => (string) env('WHMCS_INVESTIGATION_DEPT_IDS', ''),
            'ssl_verify'             => env('HSAP_WHMCS_SSL_VERIFY', true),
            '_source'                => 'env',
        ];
    }

    private function withMeta(array $data): array
    {
        if (! isset($data['_source'])) $data['_source'] = 'db';
        return $data;
    }

    private function mask(string $v): string
    {
        if ($v === '') return '';
        return strlen($v) > 8
            ? str_repeat('•', 8) . substr($v, -4)
            : str_repeat('•', strlen($v));
    }
}
