<?php

namespace App\Http\Controllers\AiPortal;

use App\Notify\Dispatcher;
use App\Notify\Message;
use App\Services\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/**
 * CRUD for notification channels. Sensitive config keys are stored as
 * `enc:<ciphertext>` so they can be round-tripped through the API without
 * leaking. The masked form is returned to the frontend.
 */
class NotificationChannelController extends Controller
{
    /** Channel type → list of config keys to mask in API responses. */
    private const SENSITIVE = [
        'telegram'        => ['bot_token'],
        'discord'         => ['webhook_url'],
        'slack'           => ['webhook_url'],
        'webhook'         => ['secret'],
        'email'           => [],
        'sms'             => ['auth_token'],
        'whatsapp_twilio' => ['auth_token'],
        'whatsapp_cloud'  => ['access_token'],
        'pushover'        => ['token', 'user'],
    ];

    public function index(Request $request)
    {
        abort_unless($request->user()->isAdminOrAbove(), 403);

        $rows = DB::table('notification_channels')->orderBy('id')->get();
        return response()->json($rows->map(fn ($r) => $this->toResource($r)));
    }

    public function types()
    {
        return response()->json(collect(Dispatcher::availableTypes())->map(fn ($t) => [
            'type'           => $t,
            'required'       => Dispatcher::requiredConfigFor($t),
            'sensitive_keys' => self::SENSITIVE[$t] ?? [],
        ]));
    }

    public function store(Request $request)
    {
        abort_unless($request->user()->isAdminOrAbove(), 403);

        $data = $request->validate([
            'type'              => 'required|string',
            'label'             => 'nullable|string|max:128',
            'config'            => 'required|array',
            'subscribed_events' => 'nullable|array',
            'enabled'           => 'nullable|boolean',
        ]);

        $this->ensureSupported($data['type']);
        $this->ensureRequiredKeys($data['type'], $data['config']);

        $config = $this->encryptSensitive($data['type'], $data['config']);

        $id = DB::table('notification_channels')->insertGetId([
            'type'              => $data['type'],
            'label'             => $data['label'] ?? ucfirst($data['type']),
            'config'            => json_encode($config),
            'subscribed_events' => json_encode($data['subscribed_events'] ?? []),
            'enabled'           => $data['enabled'] ?? true,
            'created_at'        => now(),
            'updated_at'        => now(),
        ]);

        AuditLogger::log('user', (string) $request->user()->id, $request->user()->email,
            'notify.channel_added', 'notification_channel', (string) $id,
            ['type' => $data['type']], $request);

        return response()->json($this->toResource(DB::table('notification_channels')->find($id)), 201);
    }

    public function update(Request $request, int $id)
    {
        abort_unless($request->user()->isAdminOrAbove(), 403);

        $row = DB::table('notification_channels')->find($id);
        if (! $row) abort(404);

        $data = $request->validate([
            'label'             => 'nullable|string|max:128',
            'config'            => 'nullable|array',
            'subscribed_events' => 'nullable|array',
            'enabled'           => 'nullable|boolean',
        ]);

        $update = ['updated_at' => now()];

        if (array_key_exists('label', $data) && $data['label'] !== null) {
            $update['label'] = $data['label'];
        }
        if (array_key_exists('enabled', $data) && $data['enabled'] !== null) {
            $update['enabled'] = $data['enabled'];
        }
        if (array_key_exists('subscribed_events', $data) && $data['subscribed_events'] !== null) {
            $update['subscribed_events'] = json_encode($data['subscribed_events']);
        }
        if (! empty($data['config'])) {
            // Merge with existing — incoming config can be partial. Already-encrypted
            // values pass through untouched (frontend sends "***" for masked fields).
            $existing = json_decode($row->config ?? '{}', true) ?: [];
            $merged = $existing;
            foreach ($data['config'] as $k => $v) {
                if ($v === '***' || $v === null) continue;  // masked → keep existing
                $merged[$k] = $v;
            }
            $merged = $this->encryptSensitive($row->type, $merged);
            $update['config'] = json_encode($merged);
        }

        DB::table('notification_channels')->where('id', $id)->update($update);

        AuditLogger::log('user', (string) $request->user()->id, $request->user()->email,
            'notify.channel_updated', 'notification_channel', (string) $id,
            ['changed_keys' => array_keys($data)], $request);

        return response()->json($this->toResource(DB::table('notification_channels')->find($id)));
    }

    public function destroy(Request $request, int $id)
    {
        abort_unless($request->user()->isAdminOrAbove(), 403);
        $row = DB::table('notification_channels')->find($id);
        if (! $row) abort(404);

        DB::table('notification_channels')->where('id', $id)->delete();

        AuditLogger::log('user', (string) $request->user()->id, $request->user()->email,
            'notify.channel_removed', 'notification_channel', (string) $id, [], $request);

        return response()->noContent();
    }

    /**
     * Send a test message through this channel. Returns the live result so
     * the UI can show pass/fail without polling.
     */
    public function test(Request $request, int $id)
    {
        abort_unless($request->user()->isAdminOrAbove(), 403);

        $row = DB::table('notification_channels')->find($id);
        if (! $row) abort(404);

        $impl = Dispatcher::resolve($row->type);
        if (! $impl) abort(422, 'Unknown channel type');

        $config = json_decode($row->config ?? '{}', true) ?: [];
        foreach ($config as $k => $v) {
            if (is_string($v) && str_starts_with($v, 'enc:')) {
                try { $config[$k] = decrypt(substr($v, 4)); } catch (\Throwable) {}
            }
        }

        $msg = new Message(
            event: 'test',
            title: 'Test notification from HostSeba AI Portal',
            body:  "Triggered from Settings → Notifications by " . $request->user()->email
                  . "\nTime: " . now()->toDateTimeString(),
            severity: 'info',
            url: rtrim(config('app.url'), '/') . '/settings',
        );

        $result = $impl->send($config, $msg);

        AuditLogger::log('user', (string) $request->user()->id, $request->user()->email,
            'notify.channel_tested', 'notification_channel', (string) $id,
            ['type' => $row->type, 'ok' => $result['ok'] ?? false], $request);

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

    private function toResource($row): array
    {
        $config = json_decode($row->config ?? '{}', true) ?: [];
        // Mask sensitive + already-encrypted values for client display.
        $sensitive = self::SENSITIVE[$row->type] ?? [];
        foreach ($config as $k => $v) {
            if (in_array($k, $sensitive, true) || (is_string($v) && str_starts_with($v, 'enc:'))) {
                $config[$k] = '***';
            }
        }
        return [
            'id'                => $row->id,
            'type'              => $row->type,
            'label'             => $row->label,
            'enabled'           => (bool) $row->enabled,
            'config'            => $config,
            'subscribed_events' => json_decode($row->subscribed_events ?? '[]', true) ?: [],
            'created_at'        => $row->created_at,
            'updated_at'        => $row->updated_at,
        ];
    }

    private function encryptSensitive(string $type, array $config): array
    {
        foreach (self::SENSITIVE[$type] ?? [] as $k) {
            if (isset($config[$k]) && is_string($config[$k]) && ! str_starts_with($config[$k], 'enc:')) {
                $config[$k] = 'enc:' . encrypt($config[$k]);
            }
        }
        return $config;
    }

    private function ensureSupported(string $type): void
    {
        if (! in_array($type, Dispatcher::availableTypes(), true)) {
            throw ValidationException::withMessages([
                'type' => ["Unknown channel type. Available: " . implode(', ', Dispatcher::availableTypes())],
            ]);
        }
    }

    private function ensureRequiredKeys(string $type, array $config): void
    {
        $required = Dispatcher::requiredConfigFor($type) ?: [];
        $missing = array_diff($required, array_keys($config));
        if ($missing) {
            throw ValidationException::withMessages([
                'config' => ['Missing required keys: ' . implode(', ', $missing)],
            ]);
        }
    }
}
