<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Models\AutomationWorkflowModel;

/**
 * AutomationEngine
 *
 * Executes the automation_workflows/automation_steps/automation_runs
 * schema (Phase 1) built for exactly this. Two entry points:
 *
 *   fire($triggerType, $entityType, $entityId) — called synchronously
 *   from wherever a real event happens (LeadController::store() calls
 *   fire('lead_created', 'lead', $id), etc.). Starts a run for every
 *   active workflow matching that trigger and processes it immediately
 *   up to its first 'wait' step (or completion).
 *
 *   processDueRuns() — called by a worker on a schedule. Resumes every
 *   paused run whose next_step_at has arrived, and separately checks
 *   date-based triggers (birthday, inactivity) that have no natural
 *   "event" to fire from.
 *
 * A step never blocks: send_email/assign_owner/update_field execute
 * and immediately advance; only 'wait' actually pauses a run.
 */
final class AutomationEngine
{
    public function __construct(
        private readonly AutomationWorkflowModel $workflows = new AutomationWorkflowModel(),
        private readonly EmailQueueService $queue = new EmailQueueService()
    ) {
    }

    public function fire(string $triggerType, string $entityType, int $entityId): void
    {
        foreach ($this->workflows->activeByTrigger($triggerType) as $workflow) {
            if ($this->alreadyRanRecently((int) $workflow['id'], $entityType, $entityId)) {
                continue;
            }

            $runId = $this->startRun((int) $workflow['id'], $entityType, $entityId);
            $this->advance($runId);
        }
    }

    /** Called by bin/process_automations.php on a schedule. */
    public function processDueRuns(): int
    {
        $stmt = Database::connection()->prepare(
            "SELECT id FROM automation_runs WHERE status = 'running' AND next_step_at IS NOT NULL AND next_step_at <= NOW()"
        );
        $stmt->execute();
        $dueRunIds = $stmt->fetchAll(\PDO::FETCH_COLUMN);

        foreach ($dueRunIds as $runId) {
            $this->advance((int) $runId);
        }

        return count($dueRunIds);
    }

    /** Date-based triggers with no natural "event" — call once daily from the same worker. */
    public function checkDateBasedTriggers(): void
    {
        $this->checkBirthdays();
        $this->checkInactiveCustomers();
    }

    private function startRun(int $workflowId, string $entityType, int $entityId): int
    {
        $pdo = Database::connection();
        $stmt = $pdo->prepare(
            "INSERT INTO automation_runs (workflow_id, entity_type, entity_id, status, next_step_at, started_at)
             VALUES (:workflow_id, :type, :id, 'running', NOW(), NOW())"
        );
        $stmt->execute(['workflow_id' => $workflowId, 'type' => $entityType, 'id' => $entityId]);

        return (int) $pdo->lastInsertId();
    }

    private function advance(int $runId): void
    {
        $pdo = Database::connection();
        $run = $this->fetchRun($runId);
        if ($run === null || $run['status'] !== 'running') {
            return;
        }

        $steps = $this->workflows->steps((int) $run['workflow_id']);
        if ($steps === []) {
            $this->completeRun($runId);

            return;
        }

        $currentIndex = $this->indexOfStep($steps, $run['current_step_id']);
        $nextIndex = $currentIndex === null ? 0 : $currentIndex + 1;

        if (!isset($steps[$nextIndex])) {
            $this->completeRun($runId);

            return;
        }

        $step = $steps[$nextIndex];

        try {
            $this->executeStep($run, $step);
        } catch (\Throwable $e) {
            $pdo->prepare("UPDATE automation_runs SET status = 'failed', error_message = :err WHERE id = :id")
                ->execute(['err' => substr($e->getMessage(), 0, 500), 'id' => $runId]);

            return;
        }

        if ($step['step_type'] === 'wait') {
            $config = json_decode((string) $step['config'], true) ?? [];
            $resumeAt = date('Y-m-d H:i:s', time() + ((int) ($config['delay_hours'] ?? 24) * 3600));

            $pdo->prepare('UPDATE automation_runs SET current_step_id = :step, next_step_at = :resume WHERE id = :id')
                ->execute(['step' => $step['id'], 'resume' => $resumeAt, 'id' => $runId]);

            return;
        }

        $pdo->prepare('UPDATE automation_runs SET current_step_id = :step, next_step_at = NOW() WHERE id = :id')
            ->execute(['step' => $step['id'], 'id' => $runId]);

        $this->advance($runId);
    }

    private function executeStep(array $run, array $step): void
    {
        $config = json_decode((string) $step['config'], true) ?? [];
        $entityType = $run['entity_type'];
        $entityId = (int) $run['entity_id'];

        match ($step['step_type']) {
            'send_email' => $this->executeSendEmail($entityType, $entityId, $config),
            'assign_owner' => $this->executeAssignOwner($entityId, $config),
            'update_field' => $this->executeUpdateField($entityType, $entityId, $config),
            'condition' => null,
            'webhook' => $this->executeWebhookStub(),
            'wait' => null,
            default => null,
        };
    }

    private function executeSendEmail(string $entityType, int $entityId, array $config): void
    {
        $recipient = $this->resolveEntityContact($entityType, $entityId);
        if ($recipient === null || $recipient['email'] === null) {
            return;
        }

        $subject = \App\Models\EmailTemplateModel::renderMergeFields((string) ($config['subject'] ?? 'A message from A&B Hub'), $recipient);
        $body = \App\Models\EmailTemplateModel::renderMergeFields((string) ($config['body_html'] ?? ''), $recipient);

        $this->queue->enqueue(
            $recipient['email'],
            trim(($recipient['first_name'] ?? '') . ' ' . ($recipient['last_name'] ?? '')),
            $subject,
            $body,
            businessLineId: isset($recipient['business_line_id']) ? (int) $recipient['business_line_id'] : null
        );

        ActivityLogger::record($entityType, $entityId, 'automation_email_sent', "Automation sent: \"{$subject}\"", category: 'system');
    }

    private function executeAssignOwner(int $leadId, array $config): void
    {
        if (empty($config['user_id'])) {
            return;
        }

        Database::connection()->prepare('UPDATE leads SET owner_id = :owner WHERE id = :id')
            ->execute(['owner' => $config['user_id'], 'id' => $leadId]);
    }

    private function executeUpdateField(string $entityType, int $entityId, array $config): void
    {
        if (empty($config['field'])) {
            return;
        }

        $table = $entityType === 'customer' ? 'customers' : 'leads';
        $allowedFields = ['priority', 'status', 'lead_stage_id'];

        if (!in_array($config['field'], $allowedFields, true)) {
            return;
        }

        Database::connection()->prepare("UPDATE {$table} SET {$config['field']} = :value WHERE id = :id")
            ->execute(['value' => $config['value'] ?? null, 'id' => $entityId]);
    }

    private function executeWebhookStub(): void
    {
        ActivityLogger::logAction('Automation webhook step reached (not dispatched — Phase 5+)', 'system', null, null);
    }

    private function resolveEntityContact(string $entityType, int $entityId): ?array
    {
        $table = $entityType === 'customer' ? 'customers' : 'leads';
        $stmt = Database::connection()->prepare("SELECT first_name, last_name, email, business_line_id FROM {$table} WHERE id = :id");
        $stmt->execute(['id' => $entityId]);
        $row = $stmt->fetch();

        return $row === false ? null : $row;
    }

    private function completeRun(int $runId): void
    {
        Database::connection()->prepare(
            "UPDATE automation_runs SET status = 'completed', completed_at = NOW(), next_step_at = NULL WHERE id = :id"
        )->execute(['id' => $runId]);
    }

    private function fetchRun(int $runId): ?array
    {
        $stmt = Database::connection()->prepare('SELECT * FROM automation_runs WHERE id = :id');
        $stmt->execute(['id' => $runId]);
        $row = $stmt->fetch();

        return $row === false ? null : $row;
    }

    private function indexOfStep(array $steps, ?int $stepId): ?int
    {
        if ($stepId === null) {
            return null;
        }

        foreach ($steps as $i => $step) {
            if ((int) $step['id'] === $stepId) {
                return $i;
            }
        }

        return null;
    }

    private function alreadyRanRecently(int $workflowId, string $entityType, int $entityId): bool
    {
        $stmt = Database::connection()->prepare(
            "SELECT COUNT(*) FROM automation_runs
             WHERE workflow_id = :wf AND entity_type = :type AND entity_id = :id AND started_at > DATE_SUB(NOW(), INTERVAL 1 DAY)"
        );
        $stmt->execute(['wf' => $workflowId, 'type' => $entityType, 'id' => $entityId]);

        return (int) $stmt->fetchColumn() > 0;
    }

    private function checkBirthdays(): void
    {
        $stmt = Database::connection()->prepare(
            "SELECT id FROM customers
             WHERE date_of_birth IS NOT NULL AND deleted_at IS NULL
               AND MONTH(date_of_birth) = MONTH(CURDATE()) AND DAY(date_of_birth) = DAY(CURDATE())"
        );
        $stmt->execute();

        foreach ($stmt->fetchAll(\PDO::FETCH_COLUMN) as $customerId) {
            $this->fireOncePerPeriod('customer_birthday', 'customer', (int) $customerId, 350);
        }
    }

    private function checkInactiveCustomers(int $inactiveDays = 90): void
    {
        $stmt = Database::connection()->prepare(
            "SELECT cu.id FROM customers cu
             WHERE cu.status = 'active' AND cu.deleted_at IS NULL
               AND NOT EXISTS (
                   SELECT 1 FROM appointments a
                   WHERE a.customer_id = cu.id AND a.scheduled_start > DATE_SUB(NOW(), INTERVAL :days DAY)
               )"
        );
        $stmt->bindValue('days', $inactiveDays, \PDO::PARAM_INT);
        $stmt->execute();

        foreach ($stmt->fetchAll(\PDO::FETCH_COLUMN) as $customerId) {
            $this->fireOncePerPeriod('customer_inactive_90d', 'customer', (int) $customerId, $inactiveDays);
        }
    }

    private function fireOncePerPeriod(string $triggerType, string $entityType, int $entityId, int $days): void
    {
        foreach ($this->workflows->activeByTrigger($triggerType) as $workflow) {
            $stmt = Database::connection()->prepare(
                "SELECT COUNT(*) FROM automation_runs
                 WHERE workflow_id = :wf AND entity_type = :type AND entity_id = :id
                   AND started_at > DATE_SUB(NOW(), INTERVAL :days DAY)"
            );
            $stmt->bindValue('wf', $workflow['id']);
            $stmt->bindValue('type', $entityType);
            $stmt->bindValue('id', $entityId);
            $stmt->bindValue('days', $days, \PDO::PARAM_INT);
            $stmt->execute();

            if ((int) $stmt->fetchColumn() > 0) {
                continue;
            }

            $runId = $this->startRun((int) $workflow['id'], $entityType, $entityId);
            $this->advance($runId);
        }
    }
}
