<?php

namespace FluentCrm\App\Modules\MCP\Tools;

use FluentCrm\App\Models\Funnel;
use FluentCrm\App\Models\FunnelSubscriber;
use FluentCrm\App\Modules\MCP\Helpers\MCPHelper;
use FluentCrm\App\Services\Funnel\FunnelHelper;

/**
 * Automation (funnel) MCP tools.
 *
 * Read tools (Phase 2): listAutomations, getAutomation.
 * Write tools (Phase 3): updateContactAutomationStatus.
 */
class FunnelTools
{
    // -----------------------------------------------------------------
    // Read: list-automations
    // -----------------------------------------------------------------

    public static function listAutomations($params)
    {
        $params = (array) $params;

        MCPHelper::paginationFromInput($params);

        $search   = sanitize_text_field((string) ($params['search'] ?? ''));
        $statuses = (array) ($params['statuses'] ?? []);
        $statuses = array_values(array_intersect(
            array_map('sanitize_key', $statuses),
            ['draft', 'published']
        ));
        // All fc_funnels columns. The framework rewrite made orderBy() throw
        // LogicException on column names that don't match ^[a-zA-Z0-9_\.]+$
        // — empty strings, "id ASC", "DROP TABLE", etc. — so an unguarded
        // sort_by would 500 the tool. Schema is stable (migration only adds
        // indexes), so hardcoding the column list avoids a per-request
        // SHOW COLUMNS without restricting agents to the input_schema enum.
        $allowedSortBy = [
            'id', 'type', 'title', 'trigger_name', 'status', 'conditions',
            'settings', 'created_by', 'created_at', 'updated_at',
        ];
        $sortBy = sanitize_key((string) ($params['sort_by'] ?? 'id'));
        if (!in_array($sortBy, $allowedSortBy, true)) {
            $sortBy = 'id';
        }
        $sortType = strtoupper(sanitize_text_field((string) ($params['sort_type'] ?? 'DESC')));
        $sortType = in_array($sortType, ['ASC', 'DESC'], true) ? $sortType : 'DESC';

        $query = Funnel::query()->orderBy($sortBy, $sortType);

        if ($search !== '') {
            global $wpdb;
            $like = '%' . $wpdb->esc_like($search) . '%';
            $query->where(function ($q) use ($like) {
                $q->where('title', 'LIKE', $like)
                  ->orWhere('trigger_name', 'LIKE', $like);
            });
        }

        if (!empty($statuses)) {
            $query->whereIn('status', $statuses);
        }

        $paginated = $query->paginate();

        $items = [];
        $triggerLabels = self::triggerLabelMap();
        $funnels = $paginated->items();
        $funnelIds = array_map(function ($funnel) {
            return (int) $funnel->id;
        }, $funnels);

        // One grouped query for every funnel on the current page replaces the
        // previous per-row inProgressCount() call. Keep the exact historical
        // in-progress definition (active + waiting), and make the completed
        // field match get-automation instead of counting every status.
        $subscriberCounts = [];
        if ($funnelIds) {
            $countRows = FunnelSubscriber::whereIn('funnel_id', $funnelIds)
                ->whereIn('status', ['active', 'waiting', 'completed'])
                ->select(['funnel_id', 'status'])
                ->selectRaw('COUNT(id) as total')
                ->groupBy('funnel_id')
                ->groupBy('status')
                ->get();

            foreach ($countRows as $row) {
                $funnelId = (int) $row->funnel_id;
                if (!isset($subscriberCounts[$funnelId])) {
                    $subscriberCounts[$funnelId] = [
                        'in_progress' => 0,
                        'completed'   => 0,
                    ];
                }

                if (in_array($row->status, ['active', 'waiting'], true)) {
                    $subscriberCounts[$funnelId]['in_progress'] += (int) $row->total;
                } elseif ($row->status === 'completed') {
                    $subscriberCounts[$funnelId]['completed'] = (int) $row->total;
                }
            }
        }

        // Last time this funnel's trigger actually fired, for spotting
        // automations that are still published but nothing enters any more.
        //
        // Deliberately MAX(created_at) — enrollment — and NOT
        // MAX(last_executed_time): a funnel keeps executing steps for contacts
        // who entered months ago, so last_executed_time stays fresh long after
        // the trigger went dead and would report retired funnels as active.
        //
        // Also deliberately unfiltered by status, unlike the counts above: a
        // contact who entered and was later cancelled still proves the trigger
        // fired. Opt-in because fc_funnel_subscribers is indexed on funnel_id
        // alone, so the MAX walks each funnel's rows.
        $lastEnrolled = [];
        if (!empty($params['include_last_enrolled']) && $funnelIds) {
            $enrollRows = FunnelSubscriber::whereIn('funnel_id', $funnelIds)
                ->select(['funnel_id'])
                ->selectRaw('MAX(created_at) as last_enrolled_at')
                ->groupBy('funnel_id')
                ->get();

            foreach ($enrollRows as $row) {
                $lastEnrolled[(int) $row->funnel_id] = $row->last_enrolled_at;
            }
        }

        foreach ($funnels as $funnel) {
            $counts = $subscriberCounts[(int) $funnel->id] ?? [
                'in_progress' => 0,
                'completed'   => 0,
            ];
            $item = [
                'id'                            => (int) $funnel->id,
                'title'                         => $funnel->title,
                'status'                        => $funnel->status,
                'trigger_name'                  => $funnel->trigger_name,
                'trigger_label'                 => $triggerLabels[$funnel->trigger_name] ?? $funnel->trigger_name,
                'in_progress_subscribers_count' => $counts['in_progress'],
                'completed_subscribers_count'   => $counts['completed'],
                'created_at'                    => MCPHelper::toIso8601($funnel->created_at),
                'updated_at'                    => MCPHelper::toIso8601($funnel->updated_at),
            ];

            if (!empty($params['include_last_enrolled'])) {
                $item['last_enrolled_at'] = MCPHelper::toIso8601($lastEnrolled[(int) $funnel->id] ?? null);
            }

            $items[] = $item;
        }

        return [
            'items'    => $items,
            'total'    => (int) $paginated->total(),
            'page'     => (int) $paginated->currentPage(),
            'per_page' => (int) $paginated->perPage(),
            'pages'    => (int) $paginated->lastPage(),
        ];
    }

    // -----------------------------------------------------------------
    // Read: get-automation
    // -----------------------------------------------------------------

    public static function getAutomation($params)
    {
        $params = (array) $params;
        $funnelId = (int) ($params['funnel_id'] ?? 0);

        if (!$funnelId) {
            return MCPHelper::error('invalid_param', __('funnel_id is required', 'fluent-crm'));
        }

        $funnel = Funnel::find($funnelId);
        if (!$funnel) {
            return MCPHelper::error('not_found', __('Automation not found', 'fluent-crm'), ['funnel_id' => $funnelId]);
        }

        $defaultIncludes = ['sequences', 'report'];
        // array_key_exists (not truthiness): omitted => defaults, [] =>
        // metadata only, explicit list => exactly those (TRC-005). The schema
        // documents "[] for metadata only"; the old truthy check restored the
        // defaults on [], contradicting it.
        if (array_key_exists('include', $params) && is_array($params['include'])) {
            $include = array_values(array_intersect($params['include'], ['sequences', 'report']));
        } else {
            $include = $defaultIncludes;
        }

        $triggerLabels = self::triggerLabelMap();

        $data = [
            'id'                            => (int) $funnel->id,
            'title'                         => $funnel->title,
            'status'                        => $funnel->status,
            'trigger_name'                  => $funnel->trigger_name,
            'trigger_label'                 => $triggerLabels[$funnel->trigger_name] ?? $funnel->trigger_name,
            'trigger_settings'              => is_array($funnel->settings) ? $funnel->settings : [],
            'conditions'                    => is_array($funnel->conditions) ? $funnel->conditions : [],
            'in_progress_subscribers_count' => self::inProgressCount((int) $funnel->id),
            'completed_subscribers_count'   => (int) FunnelSubscriber::where('funnel_id', $funnel->id)
                ->where('status', 'completed')
                ->count(),
            'created_at'                    => MCPHelper::toIso8601($funnel->created_at),
            'updated_at'                    => MCPHelper::toIso8601($funnel->updated_at),
        ];

        if (in_array('sequences', $include, true)) {
            $sequences = FunnelHelper::getFunnelSequences($funnel, true);
            $includeBodies = !empty($params['include_bodies']);
            $data['sequences'] = self::formatSequences($sequences, $includeBodies);
        }

        if (in_array('report', $include, true)) {
            $data['report'] = self::buildStepReport($funnel);
        }

        return $data;
    }

    /**
     * Format funnel sequences for MCP. By default we strip large body
     * fields out of `settings` (action_name=send_custom_email embeds an
     * entire campaign payload including email_body). Pass include_bodies
     * = true to get the full settings tree — review #7 (token bloat).
     */
    private static function formatSequences($sequences, $includeBodies = false)
    {
        $out = [];
        foreach ((array) $sequences as $seq) {
            $row = is_object($seq) ? get_object_vars($seq) : (array) $seq;
            $settings = $row['settings'] ?? [];
            if (!$includeBodies) {
                $settings = self::stripBodyFields($settings);
            }
            $out[] = [
                'id'           => isset($row['id']) ? (int) $row['id'] : null,
                'type'         => $row['type'] ?? null,
                'action_name'  => $row['action_name'] ?? null,
                'settings'     => $settings,
                'delay'        => $row['delay'] ?? 0,
                'delay_unit'   => $row['c_delay_unit'] ?? ($row['delay_unit'] ?? null),
                'parent_id'    => isset($row['parent_id']) ? (int) $row['parent_id'] : null,
            ];
        }
        return $out;
    }

    /**
     * Recursively redact body / html fields. Replaces them with a marker so
     * the agent knows the field exists and can re-fetch with include_bodies.
     *
     * Round-3 review B5: dropped the previous "only if >200 chars" gate —
     * any stored body field gets stripped now, regardless of size, so the
     * include_bodies=false contract is honored consistently. Rare to have a
     * truly tiny body field anyway, and the marker is shorter than most
     * email bodies.
     */
    private static function stripBodyFields($value)
    {
        $bodyKeys = ['email_body', 'body', 'body_html', 'body_text'];
        if (!is_array($value)) {
            return $value;
        }
        foreach ($value as $k => $v) {
            if (is_string($k) && in_array($k, $bodyKeys, true) && is_string($v)) {
                $len = strlen($v);
                $value[$k] = $len > 0
                    ? '[truncated — re-fetch with include_bodies=true; ' . $len . ' chars]'
                    : '';
            } elseif (is_array($v)) {
                $value[$k] = self::stripBodyFields($v);
            }
        }
        return $value;
    }

    private static function buildStepReport($funnel)
    {
        $stepCounts = FunnelSubscriber::where('funnel_id', $funnel->id)
            ->select(['last_sequence_id'])
            ->selectRaw('COUNT(id) as total')
            ->groupBy('last_sequence_id')
            ->get();

        $steps = [];
        foreach ($stepCounts as $row) {
            if (!$row->last_sequence_id) {
                continue;
            }
            $steps[] = [
                'step_id'  => (int) $row->last_sequence_id,
                'total'    => (int) $row->total,
            ];
        }
        return ['steps' => $steps];
    }

    private static function inProgressCount($funnelId)
    {
        return (int) FunnelSubscriber::where('funnel_id', $funnelId)
            ->whereIn('status', ['active', 'waiting'])
            ->count();
    }

    private static function triggerLabelMap()
    {
        $triggers = apply_filters('fluentcrm_funnel_triggers', []);
        $map = [];
        if (is_array($triggers)) {
            foreach ($triggers as $key => $config) {
                $map[$key] = $config['label'] ?? $key;
            }
        }
        return $map;
    }

    // -----------------------------------------------------------------
    // Read: list-funnel-subscribers (round-4 review P3 #11)
    // -----------------------------------------------------------------

    /**
     * List the contacts currently in a funnel filtered by subscription
     * status. Closes a real workflow gap: "this customer just upgraded —
     * pull them out of trial-onboarding" requires knowing who's in the
     * funnel first, and there was no way to find that without already
     * knowing the contact id.
     */
    public static function listFunnelSubscribers($params)
    {
        $params = (array) $params;
        $funnelId = (int) ($params['funnel_id'] ?? 0);
        if (!$funnelId) {
            return MCPHelper::error('invalid_param', __('funnel_id is required', 'fluent-crm'));
        }

        $funnel = Funnel::find($funnelId);
        if (!$funnel) {
            return MCPHelper::error('not_found', __('Automation not found', 'fluent-crm'), ['funnel_id' => $funnelId]);
        }

        $allowedStatuses = ['active', 'waiting', 'completed', 'cancelled', 'skipped'];
        $statuses = (array) ($params['statuses'] ?? ['active']);
        $statuses = array_values(array_intersect(array_map('sanitize_key', $statuses), $allowedStatuses));
        if (!$statuses) {
            $statuses = ['active'];
        }

        MCPHelper::paginationFromInput($params);

        $rows = FunnelSubscriber::with(['subscriber' => function ($q) {
            $q->select(['id', 'email', 'first_name', 'last_name', 'status', 'contact_type']);
        }])
            ->where('funnel_id', $funnelId)
            ->whereIn('status', $statuses)
            ->orderBy('id', 'DESC')
            ->paginate();

        $items = [];
        foreach ($rows->items() as $row) {
            $sub = $row->subscriber;
            if (!$sub) {
                continue;
            }
            $items[] = [
                'funnel_subscriber_id' => (int) $row->id,
                'funnel_status'        => $row->status,
                'next_sequence_id'     => $row->next_sequence_id ? (int) $row->next_sequence_id : null,
                'last_executed_at'     => MCPHelper::toIso8601($row->last_executed_time),
                'next_execution_at'    => MCPHelper::toIso8601($row->next_execution_time),
                'enrolled_at'          => MCPHelper::toIso8601($row->created_at),
                'contact'              => [
                    'id'           => (int) $sub->id,
                    'email'        => $sub->email,
                    'full_name'    => trim((string) ($sub->first_name . ' ' . $sub->last_name)),
                    'status'       => $sub->status,
                    'contact_type' => $sub->contact_type,
                ],
            ];
        }

        return [
            'items'     => $items,
            'total'     => (int) $rows->total(),
            'page'      => (int) $rows->currentPage(),
            'per_page'  => (int) $rows->perPage(),
            'pages'     => (int) $rows->lastPage(),
            'funnel'    => [
                'id'    => (int) $funnel->id,
                'title' => $funnel->title,
            ],
            'filtered_statuses' => $statuses,
        ];
    }

    // -----------------------------------------------------------------
    // Write: update-contact-automation-status
    // -----------------------------------------------------------------

    public static function updateContactAutomationStatus($params)
    {
        $params = (array) $params;
        $funnelId = (int) ($params['funnel_id'] ?? 0);
        $action   = sanitize_key((string) ($params['action'] ?? ''));

        if (!$funnelId) {
            return MCPHelper::error('invalid_param', __('funnel_id is required', 'fluent-crm'));
        }
        if (!in_array($action, ['resume', 'cancel', 'advance_now'], true)) {
            // 'pause' was intentionally dropped — FluentCRM has no native
            // paused funnel-subscriber state, and the previous mapping
            // silently cancelled. Tell the agent what the alternative is.
            if ($action === 'pause') {
                return MCPHelper::error('not_supported', __('pause is not supported — FluentCRM has no paused state for funnel subscribers. Use cancel to stop processing (reversible from the UI), or wait for a real benchmark.', 'fluent-crm'), [
                    'allowed_actions' => ['resume', 'cancel', 'advance_now'],
                ]);
            }
            return MCPHelper::error('invalid_param', __('Invalid action', 'fluent-crm'), [
                'allowed_actions' => ['resume', 'cancel', 'advance_now'],
            ]);
        }

        $contact = MCPHelper::resolveContact($params);
        if (is_wp_error($contact)) {
            return $contact;
        }

        $funnel = Funnel::find($funnelId);
        if (!$funnel) {
            return MCPHelper::error('not_found', __('Automation not found', 'fluent-crm'), ['funnel_id' => $funnelId]);
        }

        $row = FunnelSubscriber::where('funnel_id', $funnelId)
            ->where('subscriber_id', $contact->id)
            ->first();
        if (!$row) {
            return MCPHelper::error('not_found', __('Contact is not enrolled in this automation', 'fluent-crm'));
        }

        $previousStatus = $row->status;

        if ($row->status === 'completed') {
            return MCPHelper::error('not_supported', __('Automation is already completed for this contact', 'fluent-crm'), [
                'status' => $row->status,
            ]);
        }

        if ($action === 'cancel') {
            $row->status = 'cancelled';
            $row->save();
        } elseif ($action === 'resume') {
            $row->status = 'active';
            if (!$row->next_execution_time) {
                $row->next_execution_time = gmdate('Y-m-d H:i:s', current_time('timestamp') + 60);
            }
            $row->save();
        } elseif ($action === 'advance_now') {
            $sequenceId = (int) ($params['advance_to_sequence_id'] ?? 0);
            if (!$sequenceId) {
                return MCPHelper::error('invalid_param', __('advance_to_sequence_id is required for advance_now', 'fluent-crm'));
            }
            $sequence = \FluentCrm\App\Models\FunnelSequence::where('id', $sequenceId)
                ->where('funnel_id', $funnelId)
                ->first();
            if (!$sequence) {
                return MCPHelper::error('not_found', __('Target sequence not found in this automation', 'fluent-crm'));
            }

            // If the contact is waiting on a benchmark, mark the benchmark as
            // skipped so reports stay accurate (matches the controller's path).
            if ($row->status === 'waiting') {
                $benchmarkSeq = \FluentCrm\App\Models\FunnelSequence::find($row->next_sequence_id);
                if ($benchmarkSeq) {
                    \FluentCrm\App\Models\FunnelMetric::updateOrCreate(
                        [
                            'funnel_id'     => $funnelId,
                            'sequence_id'   => $benchmarkSeq->id,
                            'subscriber_id' => $contact->id,
                        ],
                        [
                            'benchmark_value'    => 0,
                            'benchmark_currency' => 'USD',
                            'status'             => 'skipped',
                            'notes'              => __('Skipped via MCP advance_now', 'fluent-crm'),
                        ]
                    );
                    \FluentCrm\App\Services\Funnel\FunnelHelper::changeFunnelSubSequenceStatus($row->id, $benchmarkSeq->id, 'skipped');
                }
            }

            $prev = \FluentCrm\App\Models\FunnelSequence::where('funnel_id', $funnelId)
                ->where('sequence', '<', $sequence->sequence)
                ->orderBy('sequence', 'DESC')
                ->first();

            $row->last_sequence_id = $prev ? $prev->id : 0;
            $row->next_sequence_id = $sequence->id;
            $row->next_sequence    = $sequence->sequence;
            $row->status           = 'active';
            $row->next_execution_time = current_time('mysql');
            $row->save();
        }

        $row = FunnelSubscriber::find($row->id);

        return [
            'ok'              => true,
            'action'          => $action,
            'previous_status' => $previousStatus,
            'current_status'  => $row->status,
            'funnel_subscriber' => [
                'id'                => (int) $row->id,
                'funnel_id'         => (int) $row->funnel_id,
                'subscriber_id'     => (int) $row->subscriber_id,
                'status'            => $row->status,
                'next_sequence_id'  => $row->next_sequence_id ? (int) $row->next_sequence_id : null,
                'next_execution_time' => MCPHelper::toIso8601($row->next_execution_time),
            ],
        ];
    }
}
