<?php
namespace App\Services;

use App\Core\Config;
use App\Core\Database;

/**
 * Fires notification events (FR-SUP-080 to FR-SUP-085). Every dispatch is
 * recorded to notification_log regardless of channel or outcome, so the
 * "every notification sent against the ticket" requirement holds even
 * when SMTP is not configured on a dev box.
 */
class NotificationService
{
    public static function send(string $eventCode, array $ticket, array $extra = []): void
    {
        $placeholders = self::placeholdersFor($ticket, $extra);

        self::sendEmailIfApplicable($eventCode, $ticket, $placeholders);
        self::sendInAppIfApplicable($eventCode, $ticket, $placeholders);
    }

    /**
     * Builds the {{ticket_no}}, {{contact_name}}, {{agent_name}}, etc. placeholder map for
     * a ticket. Public so TicketService can resolve the same placeholders inside a canned
     * response or hand-typed reply (FR-SUP-034) — not just inside notification templates.
     */
    public static function placeholdersFor(array $ticket, array $extra = []): array
    {
        $contact = Database::one('SELECT * FROM contacts WHERE id = :id', ['id' => $ticket['contact_id'] ?? 0]) ?: [];
        $customer = Database::one('SELECT * FROM customers WHERE id = :id', ['id' => $ticket['customer_id'] ?? 0]) ?: [];
        $priority = Database::one('SELECT * FROM priorities WHERE id = :id', ['id' => $ticket['priority_id'] ?? 0]) ?: [];
        $assignee = !empty($ticket['assignee_id']) ? Database::one('SELECT * FROM users WHERE id = :id', ['id' => $ticket['assignee_id']]) : null;

        return array_merge([
            'ticket_no' => $ticket['ticket_no'] ?? '',
            'subject' => $ticket['subject'] ?? '',
            'contact_name' => $contact['name'] ?? 'Customer',
            'company_name' => Config::get('app.name'),
            'priority' => $priority['name'] ?? '',
            'status' => status_label($ticket['status'] ?? ''),
            'response_due' => !empty($ticket['response_due_at']) ? fmt_dt($ticket['response_due_at']) : '',
            'agent_name' => $assignee['name'] ?? 'Support team',
            'customer_name' => $customer['name'] ?? '',
        ], array_map('strval', $extra));
    }

    public static function fill(string $template, array $placeholders): string
    {
        $search = array_map(fn($k) => '{{' . $k . '}}', array_keys($placeholders));
        return str_replace($search, array_values($placeholders), $template);
    }

    private static function customerFacingEvents(): array
    {
        return ['ticket_created_ack', 'ticket_public_reply', 'ticket_status_change', 'ticket_closed'];
    }

    private static function sendEmailIfApplicable(string $eventCode, array $ticket, array $placeholders): void
    {
        if (!in_array($eventCode, self::customerFacingEvents(), true)) return;

        $tpl = Database::one("SELECT * FROM notification_templates WHERE event_code = :e AND channel = 'email' AND is_active = 1", ['e' => $eventCode]);
        $contact = Database::one('SELECT * FROM contacts WHERE id = :id', ['id' => $ticket['contact_id'] ?? 0]);
        if (!$tpl || !$contact || empty($contact['email'])) return;

        $subject = self::fill($tpl['subject'] ?? '', $placeholders);
        $body = self::fill($tpl['body'], $placeholders);

        $sent = Mailer::send($contact['email'], $contact['name'], $subject, $body);

        Database::insert('notification_log', [
            'ticket_id' => $ticket['id'] ?? null,
            'event_code' => $eventCode,
            'channel' => 'email',
            'recipient' => $contact['email'],
            'subject' => $subject,
            'body' => $body,
            'status' => $sent ? 'sent' : 'failed',
            'error' => $sent ? null : 'SMTP delivery failed or not configured',
        ]);
    }

    /** FR-SUP-086: retry failed email notifications on a configurable policy. */
    public static function retryFailed(int $maxAttempts = 3): array
    {
        $summary = ['retried' => 0, 'gave_up' => 0];
        $failed = Database::all(
            "SELECT * FROM notification_log WHERE channel = 'email' AND status = 'failed' AND retry_count < :max
             AND created_at > (NOW() - INTERVAL 2 DAY)",
            ['max' => $maxAttempts]
        );
        foreach ($failed as $row) {
            $sent = Mailer::send($row['recipient'], $row['recipient'], (string)$row['subject'], (string)$row['body']);
            Database::update('notification_log', [
                'status' => $sent ? 'sent' : 'failed',
                'retry_count' => (int)$row['retry_count'] + 1,
            ], 'id = :id', ['id' => $row['id']]);
            if ($sent) $summary['retried']++;
            elseif ((int)$row['retry_count'] + 1 >= $maxAttempts) $summary['gave_up']++;
        }
        return $summary;
    }

    private static function sendInAppIfApplicable(string $eventCode, array $ticket, array $placeholders): void
    {
        $tpl = Database::one("SELECT * FROM notification_templates WHERE event_code = :e AND channel = 'in_app' AND is_active = 1", ['e' => $eventCode]);
        if (!$tpl) return;

        $recipients = self::recipientsFor($eventCode, $ticket);
        $message = self::fill($tpl['body'], $placeholders);

        foreach ($recipients as $userId) {
            $user = Database::one('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
            if (!$user || !$user['in_app_alerts']) continue;

            Database::insert('in_app_notifications', [
                'user_id' => $userId,
                'ticket_id' => $ticket['id'] ?? null,
                'message' => $message,
            ]);
            Database::insert('notification_log', [
                'ticket_id' => $ticket['id'] ?? null,
                'event_code' => $eventCode,
                'channel' => 'in_app',
                'recipient' => $user['email'],
                'subject' => null,
                'status' => 'sent',
            ]);
        }
    }

    private static function recipientsFor(string $eventCode, array $ticket): array
    {
        $ids = [];
        $assigneeId = $ticket['assignee_id'] ?? null;
        $leadIds = array_column(Database::all(
            "SELECT u.id FROM users u JOIN roles r ON r.id = u.role_id WHERE r.code = 'LEAD' AND u.is_active = 1"
        ), 'id');
        $headIds = array_column(Database::all(
            "SELECT u.id FROM users u JOIN roles r ON r.id = u.role_id WHERE r.code = 'HEAD' AND u.is_active = 1"
        ), 'id');

        switch ($eventCode) {
            case 'ticket_assigned':
            case 'ticket_customer_reply':
                if ($assigneeId) $ids[] = $assigneeId;
                break;
            case 'sla_warning':
                if ($assigneeId) $ids[] = $assigneeId;
                $ids = array_merge($ids, $leadIds);
                break;
            case 'sla_breach':
            case 'ticket_escalated':
                $ids = array_merge($ids, $leadIds, $headIds);
                if ($assigneeId) $ids[] = $assigneeId;
                break;
        }
        return array_unique($ids);
    }
}
