<?php

namespace App\Console\Commands;

use App\Enum\Communication\CommunicationChannel;
use App\Enum\Communication\CommunicationStatus;
use App\Models\Communication\Communication;
use Illuminate\Console\Command;

/**
 * Force-closes voice calls left in a non-final status.
 *
 * A call gets orphaned when its terminating Twilio callback never lands or fails
 * (e.g. the status-callback transaction rolled back). The Communication then sits
 * in initiated/ringing/in_progress forever and clutters the dialler dashboard's
 * "Active Calls" panel. This sweep gives those calls a sensible terminal status —
 * Completed if they were answered, Missed otherwise — so they clear out.
 *
 * WIP is untouched: wip:generate-call-entries owns billing and keys off recording
 * duration + the wip_created flag, independently of call status.
 */
class CloseStaleCalls extends Command
{
    protected $signature = 'dialler:close-stale-calls
        {--hours=2 : Minimum age in hours before an unfinished call is force-closed}
        {--dry-run : List what would change without writing anything}';

    protected $description = 'Force-close voice calls stuck in a non-final status so they clear from the dialler dashboard.';

    public function handle(): int
    {
        $hours     = max(1, (int) $this->option('hours'));
        $dryRun     = (bool) $this->option('dry-run');
        $threshold = now()->subHours($hours);

        // Console context: the BelongsToSolicitor global scope is inactive, so a
        // single pass covers every tenant.
        $stale = Communication::query()
            ->where('channel', CommunicationChannel::Voice->value)
            ->whereIn('status', [
                CommunicationStatus::Initiated->value,
                CommunicationStatus::Ringing->value,
                CommunicationStatus::InProgress->value,
            ])
            ->where('started_at', '<', $threshold)
            ->orderBy('id')
            ->get();

        if ($stale->isEmpty()) {
            $this->info("No stale calls older than {$hours}h found.");

            return self::SUCCESS;
        }

        $completed = 0;
        $missed    = 0;

        foreach ($stale as $communication) {
            // Answered → a real conversation (Completed); never answered → Missed.
            // Mirrors HandleCallStatusCallbackAction's own status mapping.
            $answered = $communication->answered_at !== null;
            $status   = $answered ? CommunicationStatus::Completed : CommunicationStatus::Missed;
            $endedAt  = $communication->ended_at
                ?? $communication->answered_at
                ?? $communication->started_at
                ?? now();

            if ($dryRun) {
                $this->line(sprintf(
                    '  #%d  %s → %s  (started %s)',
                    $communication->id,
                    $communication->status,
                    $status->value,
                    optional($communication->started_at)->diffForHumans() ?? 'unknown',
                ));
            } else {
                $communication->update([
                    'status'   => $status->value,
                    'ended_at' => $endedAt,
                ]);
            }

            $answered ? $completed++ : $missed++;
        }

        $verb = $dryRun ? 'Would close' : 'Closed';
        $this->info("{$verb} {$stale->count()} stale call(s): {$completed} completed, {$missed} missed.");

        return self::SUCCESS;
    }
}
