<?php

declare(strict_types=1);

final class NotificationEmailsRepository
{
    public function __construct(private readonly PDO $pdo)
    {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listAll(bool $verifiedOnly = false): array
    {
        $sql = 'SELECT ne.*, CASE WHEN ss.default_notification_email_id = ne.id THEN 1 ELSE 0 END AS is_default
                FROM notification_emails ne
                LEFT JOIN site_settings ss ON ss.id = 1';
        if ($verifiedOnly) {
            $sql .= " WHERE ne.verified_at IS NOT NULL AND ne.verified_at <> ''";
        }
        $sql .= ' ORDER BY ne.label ASC, ne.address ASC';
        $statement = $this->pdo->query($sql);
        return array_map([$this, 'hydrate'], $statement ? ($statement->fetchAll(PDO::FETCH_ASSOC) ?: []) : []);
    }

    public function findById(string $id): ?array
    {
        $statement = $this->pdo->prepare('SELECT * FROM notification_emails WHERE id = :id LIMIT 1');
        $statement->execute([':id' => trim($id)]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        return is_array($row) ? $this->hydrate($row) : null;
    }

    public function create(string $label, string $address, string $userId): array
    {
        $address = myronNormalizeEmail($address);
        if (!filter_var($address, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('A valid destination email is required.');
        }
        $id = 'ne_' . bin2hex(random_bytes(8));
        $token = myronBase64UrlEncode(random_bytes(24));
        $statement = $this->pdo->prepare(
            'INSERT INTO notification_emails (
                id, address, label, verified_at, verification_token_hash, verification_sent_at, created_by, created_at
             ) VALUES (
                :id, :address, :label, NULL, :verification_token_hash, :verification_sent_at, :created_by, :created_at
             )'
        );
        $statement->execute([
            ':id' => $id,
            ':address' => $address,
            ':label' => trim($label) !== '' ? trim($label) : $address,
            ':verification_token_hash' => hash('sha256', $token),
            ':verification_sent_at' => myronNow(),
            ':created_by' => $userId,
            ':created_at' => myronNow(),
        ]);
        $record = $this->findById($id) ?? [];
        $record['_token'] = $token;
        return $record;
    }

    public function rotateToken(string $id): array
    {
        $token = myronBase64UrlEncode(random_bytes(24));
        $statement = $this->pdo->prepare(
            'UPDATE notification_emails
             SET verification_token_hash = :hash, verification_sent_at = :sent_at
             WHERE id = :id'
        );
        $statement->execute([
            ':id' => trim($id),
            ':hash' => hash('sha256', $token),
            ':sent_at' => myronNow(),
        ]);
        $record = $this->findById($id);
        if ($record === null) {
            throw new InvalidArgumentException('Verified destination not found.');
        }
        $record['_token'] = $token;
        return $record;
    }

    public function verify(string $id, string $token): bool
    {
        $record = $this->findById($id);
        if ($record === null || (string) ($record['verified_at'] ?? '') !== '') {
            return false;
        }
        $hash = (string) ($record['verification_token_hash'] ?? '');
        $sentAt = strtotime((string) ($record['verification_sent_at'] ?? ''));
        if ($hash === '' || $sentAt < (time() - 86400) || !hash_equals($hash, hash('sha256', $token))) {
            return false;
        }
        $statement = $this->pdo->prepare(
            'UPDATE notification_emails
             SET verified_at = :verified_at, verification_token_hash = NULL
             WHERE id = :id'
        );
        $statement->execute([':id' => trim($id), ':verified_at' => myronNow()]);
        return true;
    }

    public function setDefault(string $id): void
    {
        $record = $this->findById($id);
        if ($record === null || (string) ($record['verified_at'] ?? '') === '') {
            throw new InvalidArgumentException('Only verified destinations can be the default.');
        }
        $statement = $this->pdo->prepare('UPDATE site_settings SET default_notification_email_id = :id WHERE id = 1');
        $statement->execute([':id' => trim($id)]);
    }

    public function delete(string $id, MicFormRepository $forms): void
    {
        $forms->clearNotificationEmail($id);
        $statement = $this->pdo->prepare('UPDATE site_settings SET default_notification_email_id = NULL WHERE default_notification_email_id = :id');
        $statement->execute([':id' => trim($id)]);
        $delete = $this->pdo->prepare('DELETE FROM notification_emails WHERE id = :id');
        $delete->execute([':id' => trim($id)]);
    }

    public function resolveRecipientForForm(array $form): ?array
    {
        $id = trim((string) ($form['notification_email_id'] ?? ''));
        if ($id === 'none') {
            return null;
        }
        if ($id !== '') {
            $record = $this->findById($id);
            if ($record !== null && (string) ($record['verified_at'] ?? '') !== '') {
                return $record;
            }
        }
        $statement = $this->pdo->query(
            'SELECT ne.*
             FROM site_settings ss
             INNER JOIN notification_emails ne ON ne.id = ss.default_notification_email_id
             WHERE ss.id = 1 AND ne.verified_at IS NOT NULL AND ne.verified_at <> \'\'
             LIMIT 1'
        );
        $row = $statement ? $statement->fetch(PDO::FETCH_ASSOC) : false;
        return is_array($row) ? $this->hydrate($row) : null;
    }

    private function hydrate(array $row): array
    {
        return [
            'id' => (string) ($row['id'] ?? ''),
            'address' => (string) ($row['address'] ?? ''),
            'label' => (string) ($row['label'] ?? ''),
            'verified_at' => (string) ($row['verified_at'] ?? ''),
            'verification_token_hash' => (string) ($row['verification_token_hash'] ?? ''),
            'verification_sent_at' => (string) ($row['verification_sent_at'] ?? ''),
            'created_by' => (string) ($row['created_by'] ?? ''),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'is_default' => (int) ($row['is_default'] ?? 0) === 1,
        ];
    }
}
