<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;

/**
 * ChatConversation Model
 * 
 * Represents a chat conversation session with a visitor.
 * 
 * Requirements: 4.1, 4.4, 8.3
 * 
 * @property int $id
 * @property string $session_id
 * @property int|null $category_id
 * @property int|null $agent_id
 * @property string|null $visitor_name
 * @property string|null $visitor_email
 * @property string|null $visitor_phone
 * @property string|null $visitor_ip
 * @property string|null $user_agent
 * @property array|null $metadata
 * @property array|null $custom_fields
 * @property string|null $initial_message
 * @property string $status
 * @property string $priority
 * @property \Carbon\Carbon|null $started_at
 * @property \Carbon\Carbon|null $first_response_at
 * @property \Carbon\Carbon|null $ended_at
 * @property \Carbon\Carbon|null $last_message_at
 * @property int $wait_time
 * @property int $duration
 * @property string|null $resolution_notes
 * @property int|null $ended_by
 * @property string|null $end_reason
 * @property int|null $satisfaction_rating
 * @property string|null $satisfaction_comment
 * @property int $message_count
 * @property int $visitor_message_count
 * @property int $agent_message_count
 */
class ChatConversation extends Model
{
    use SoftDeletes;

    /**
     * Status constants
     */
    const STATUS_WAITING = 'waiting';
    const STATUS_ACTIVE = 'active';
    const STATUS_RESOLVED = 'resolved';
    const STATUS_MISSED = 'missed';
    const STATUS_CLOSED = 'closed';

    /**
     * Priority constants
     */
    const PRIORITY_LOW = 'low';
    const PRIORITY_NORMAL = 'normal';
    const PRIORITY_HIGH = 'high';

    /**
     * End reasons
     */
    const END_REASON_AGENT_CLOSED = 'agent_closed';
    const END_REASON_VISITOR_CLOSED = 'visitor_closed';
    const END_REASON_TIMEOUT = 'timeout';
    const END_REASON_AGENT_UNAVAILABLE = 'agent_unavailable';
    const END_REASON_SYSTEM = 'system';

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'session_id',
        'category_id',
        'agent_id',
        'visitor_name',
        'visitor_email',
        'visitor_phone',
        'visitor_ip',
        'user_agent',
        'metadata',
        'custom_fields',
        'initial_message',
        'status',
        'priority',
        'started_at',
        'first_response_at',
        'ended_at',
        'last_message_at',
        'wait_time',
        'duration',
        'resolution_notes',
        'ended_by',
        'end_reason',
        'satisfaction_rating',
        'satisfaction_comment',
        'message_count',
        'visitor_message_count',
        'agent_message_count',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'metadata' => 'array',
        'custom_fields' => 'array',
        'started_at' => 'datetime',
        'first_response_at' => 'datetime',
        'ended_at' => 'datetime',
        'last_message_at' => 'datetime',
        'wait_time' => 'integer',
        'duration' => 'integer',
        'satisfaction_rating' => 'integer',
        'message_count' => 'integer',
        'visitor_message_count' => 'integer',
        'agent_message_count' => 'integer',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'deleted_at' => 'datetime',
    ];

    /**
     * Status color mapping for UI.
     */
    public static function statusColors(): array
    {
        return [
            self::STATUS_WAITING => 'bg-yellow-100 text-yellow-800',
            self::STATUS_ACTIVE => 'bg-green-100 text-green-800',
            self::STATUS_RESOLVED => 'bg-blue-100 text-blue-800',
            self::STATUS_MISSED => 'bg-red-100 text-red-800',
            self::STATUS_CLOSED => 'bg-gray-100 text-gray-800',
        ];
    }

    /**
     * Boot the model.
     */
    protected static function booted(): void
    {
        static::creating(function (self $conversation) {
            if (empty($conversation->session_id)) {
                $conversation->session_id = self::generateSessionId();
            }
            if (empty($conversation->started_at)) {
                $conversation->started_at = now();
            }
        });

        static::created(function (self $conversation) {
            // Increment category conversation count
            if ($conversation->category_id) {
                ChatCategory::where('id', $conversation->category_id)
                    ->increment('conversations_count');
            }
        });
    }

    /**
     * Generate unique session ID.
     */
    public static function generateSessionId(): string
    {
        return 'chat_' . Str::random(32);
    }

    /**
     * Get the category this conversation belongs to.
     */
    public function category(): BelongsTo
    {
        return $this->belongsTo(ChatCategory::class, 'category_id');
    }

    /**
     * Get the agent handling this conversation.
     */
    public function agent(): BelongsTo
    {
        return $this->belongsTo(User::class, 'agent_id');
    }

    /**
     * Get the user who ended this conversation.
     */
    public function endedByUser(): BelongsTo
    {
        return $this->belongsTo(User::class, 'ended_by');
    }

    /**
     * Get all messages in this conversation.
     */
    public function messages(): HasMany
    {
        return $this->hasMany(ChatMessage::class, 'conversation_id')
            ->orderBy('created_at');
    }

    /**
     * Get latest messages first.
     */
    public function latestMessages(): HasMany
    {
        return $this->hasMany(ChatMessage::class, 'conversation_id')
            ->latest();
    }

    /**
     * Get the last message in this conversation.
     */
    public function lastMessage(): \Illuminate\Database\Eloquent\Relations\HasOne
    {
        return $this->hasOne(ChatMessage::class, 'conversation_id')
            ->latest();
    }

    /**
     * Scope for waiting conversations.
     */
    public function scopeWaiting(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_WAITING);
    }

    /**
     * Scope for active conversations.
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_ACTIVE);
    }

    /**
     * Scope for resolved conversations.
     */
    public function scopeResolved(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_RESOLVED);
    }

    /**
     * Scope for missed conversations (no agent picked up).
     */
    public function scopeMissed(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_MISSED);
    }

    /**
     * Scope for closed conversations.
     */
    public function scopeClosed(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_CLOSED);
    }

    /**
     * Scope for open conversations (waiting or active).
     */
    public function scopeOpen(Builder $query): Builder
    {
        return $query->whereIn('status', [self::STATUS_WAITING, self::STATUS_ACTIVE]);
    }

    /**
     * Scope for conversations assigned to specific agent.
     */
    public function scopeAssignedTo(Builder $query, int $agentId): Builder
    {
        return $query->where('agent_id', $agentId);
    }

    /**
     * Scope for unassigned conversations.
     */
    public function scopeUnassigned(Builder $query): Builder
    {
        return $query->whereNull('agent_id');
    }

    /**
     * Scope for conversations in a category.
     */
    public function scopeInCategory(Builder $query, int $categoryId): Builder
    {
        return $query->where('category_id', $categoryId);
    }

    /**
     * Scope for high priority conversations.
     */
    public function scopeHighPriority(Builder $query): Builder
    {
        return $query->where('priority', self::PRIORITY_HIGH);
    }

    /**
     * Scope for searching conversations.
     */
    public function scopeSearch(Builder $query, string $search): Builder
    {
        return $query->where(function ($q) use ($search) {
            $q->where('visitor_name', 'like', "%{$search}%")
              ->orWhere('visitor_email', 'like', "%{$search}%")
              ->orWhere('initial_message', 'like', "%{$search}%")
              ->orWhere('session_id', 'like', "%{$search}%");
        });
    }

    /**
     * Scope for date range filtering.
     */
    public function scopeDateRange(Builder $query, $startDate, $endDate): Builder
    {
        return $query->whereBetween('started_at', [$startDate, $endDate]);
    }

    /**
     * Check if conversation is waiting.
     */
    public function isWaiting(): bool
    {
        return $this->status === self::STATUS_WAITING;
    }

    /**
     * Check if conversation is active.
     */
    public function isActive(): bool
    {
        return $this->status === self::STATUS_ACTIVE;
    }

    /**
     * Check if conversation is closed.
     */
    public function isClosed(): bool
    {
        return in_array($this->status, [self::STATUS_RESOLVED, self::STATUS_MISSED, self::STATUS_CLOSED]);
    }

    /**
     * Check if conversation has an agent.
     */
    public function hasAgent(): bool
    {
        return !is_null($this->agent_id);
    }

    /**
     * Assign agent to conversation.
     */
    public function assignAgent(int $agentId): void
    {
        $updateData = ['agent_id' => $agentId];
        
        // If this is first assignment, set first_response_at and calculate wait time
        if ($this->status === self::STATUS_WAITING) {
            $updateData['status'] = self::STATUS_ACTIVE;
            $updateData['first_response_at'] = now();
            
            if ($this->started_at) {
                $updateData['wait_time'] = $this->started_at->diffInSeconds(now());
            }
        }
        
        $this->update($updateData);
    }

    /**
     * Unassign agent from conversation.
     */
    public function unassignAgent(): void
    {
        $this->update([
            'agent_id' => null,
            'status' => self::STATUS_WAITING,
        ]);
    }

    /**
     * Close the conversation.
     */
    public function close(?int $endedBy = null, ?string $reason = null, ?string $notes = null): void
    {
        $updateData = [
            'status' => self::STATUS_CLOSED,
            'ended_at' => now(),
            'end_reason' => $reason ?? self::END_REASON_AGENT_CLOSED,
            'resolution_notes' => $notes,
        ];
        
        if ($endedBy) {
            $updateData['ended_by'] = $endedBy;
        }
        
        // Calculate duration if started_at is set
        if ($this->started_at) {
            $updateData['duration'] = $this->started_at->diffInSeconds(now());
        }
        
        $this->update($updateData);
    }

    /**
     * Mark as resolved.
     */
    public function resolve(?int $endedBy = null, ?string $notes = null): void
    {
        $updateData = [
            'status' => self::STATUS_RESOLVED,
            'ended_at' => now(),
            'end_reason' => self::END_REASON_AGENT_CLOSED,
            'resolution_notes' => $notes,
        ];
        
        if ($endedBy) {
            $updateData['ended_by'] = $endedBy;
        }
        
        if ($this->started_at) {
            $updateData['duration'] = $this->started_at->diffInSeconds(now());
        }
        
        $this->update($updateData);
    }

    /**
     * Mark as missed (no agent available).
     */
    public function markAsMissed(): void
    {
        $updateData = [
            'status' => self::STATUS_MISSED,
            'ended_at' => now(),
            'end_reason' => self::END_REASON_AGENT_UNAVAILABLE,
        ];
        
        if ($this->started_at) {
            $updateData['duration'] = $this->started_at->diffInSeconds(now());
        }
        
        $this->update($updateData);
    }

    /**
     * Record visitor satisfaction.
     */
    public function recordSatisfaction(int $rating, ?string $comment = null): void
    {
        $this->update([
            'satisfaction_rating' => min(5, max(1, $rating)),
            'satisfaction_comment' => $comment,
        ]);
    }

    /**
     * Update last message timestamp.
     */
    public function updateLastMessage(): void
    {
        $this->update(['last_message_at' => now()]);
    }

    /**
     * Increment message counts.
     */
    public function incrementMessageCount(bool $isAgent): void
    {
        $this->increment('message_count');
        
        if ($isAgent) {
            $this->increment('agent_message_count');
        } else {
            $this->increment('visitor_message_count');
        }
        
        $this->updateLastMessage();
    }

    /**
     * Get the status badge HTML.
     */
    public function getStatusBadgeAttribute(): string
    {
        $color = self::statusColors()[$this->status] ?? 'bg-gray-100 text-gray-800';
        return '<span class="px-3 py-1 rounded-full text-xs font-semibold ' . $color . '">' .
               ucfirst($this->status) . '</span>';
    }

    /**
     * Get formatted wait time.
     */
    public function getFormattedWaitTimeAttribute(): string
    {
        $seconds = $this->wait_time;
        
        if ($seconds < 60) {
            return $seconds . 's';
        }
        
        $minutes = floor($seconds / 60);
        $remainingSeconds = $seconds % 60;
        
        return $minutes . 'm ' . $remainingSeconds . 's';
    }

    /**
     * Get formatted duration.
     */
    public function getFormattedDurationAttribute(): string
    {
        $seconds = $this->duration;
        
        if ($seconds < 60) {
            return $seconds . 's';
        }
        
        if ($seconds < 3600) {
            $minutes = floor($seconds / 60);
            return $minutes . 'm';
        }
        
        $hours = floor($seconds / 3600);
        $minutes = floor(($seconds % 3600) / 60);
        
        return $hours . 'h ' . $minutes . 'm';
    }

    /**
     * Get visitor display name.
     */
    public function getVisitorDisplayNameAttribute(): string
    {
        return $this->visitor_name ?: 'Guest ' . substr($this->session_id, -6);
    }

    /**
     * Get average satisfaction rating.
     */
    public static function getAverageSatisfaction(): float
    {
        return self::whereNotNull('satisfaction_rating')
            ->avg('satisfaction_rating') ?? 0;
    }

    /**
     * Get average wait time.
     */
    public static function getAverageWaitTime(): int
    {
        return self::whereNotNull('wait_time')
            ->avg('wait_time') ?? 0;
    }

    /**
     * Get average duration.
     */
    public static function getAverageDuration(): int
    {
        return self::whereNotNull('duration')
            ->avg('duration') ?? 0;
    }
}
