<?php

namespace App\Services;

use App\Models\Ticket;
use App\Models\TicketReply;
use App\Models\User;
use App\Mail\Communication\TicketStatusMail;
use App\Mail\Communication\TicketNotificationMail;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;

/**
 * TicketService
 * 
 * Handles business logic for the ticketing system.
 * Manages ticket creation, status transitions, notifications, and replies.
 * 
 * Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 5.1, 5.2, 8.1
 */
class TicketService
{
    /**
     * Create a new ticket.
     *
     * @param int $userId The user creating the ticket
     * @param array $data Ticket data (title, description, priority, category)
     * @return Ticket
     */
    public function createTicket(int $userId, array $data): Ticket
    {
        return DB::transaction(function () use ($userId, $data) {
            // Create the ticket
            $ticket = Ticket::create([
                'user_id' => $userId,
                'title' => strip_tags($data['title']),
                'description' => strip_tags($data['description']),
                'priority' => $data['priority'] ?? Ticket::PRIORITY_MEDIUM,
                'category' => $data['category'] ?? null,
                'status' => Ticket::STATUS_PENDING,
            ]);

            // Notify Super Managers and Managers about new ticket
            $this->notifyAdministrators($ticket);

            return $ticket;
        });
    }

    /**
     * Update ticket status.
     *
     * @param Ticket $ticket
     * @param string $status
     * @param int|null $userId User who updated the status
     * @param string|null $notes Resolution notes
     * @return Ticket
     */
    public function updateStatus(Ticket $ticket, string $status, ?int $userId = null, ?string $notes = null): Ticket
    {
        $previousStatus = $ticket->status;

        DB::transaction(function () use ($ticket, $status, $userId, $notes) {
            $updateData = [
                'status' => $status,
                'last_activity_at' => now(),
            ];

            if ($status === Ticket::STATUS_RESOLVED && $userId) {
                $updateData['resolved_at'] = now();
                $updateData['resolved_by'] = $userId;
                $updateData['resolution_notes'] = $notes;
            }

            $ticket->update($updateData);

            // Send status change notification to ticket owner
            $this->sendStatusChangeNotification($ticket, $previousStatus ?? $status);
        });

        return $ticket->fresh();
    }

    /**
     * Add a reply to a ticket.
     *
     * @param Ticket $ticket
     * @param int $userId
     * @param string $message
     * @param bool $isInternal
     * @return TicketReply
     */
    public function addReply(Ticket $ticket, int $userId, string $message, bool $isInternal = false): TicketReply
    {
        return DB::transaction(function () use ($ticket, $userId, $message, $isInternal) {
            $reply = TicketReply::create([
                'ticket_id' => $ticket->id,
                'user_id' => $userId,
                'message' => strip_tags($message),
                'is_internal' => $isInternal,
            ]);

            // Update ticket last activity
            $ticket->touchLastActivity();

            // If this is the first agent reply, change status to in_progress
            if (!$isInternal && $ticket->status === Ticket::STATUS_PENDING) {
                $ticket->update(['status' => Ticket::STATUS_IN_PROGRESS]);
            }

            // Notify ticket owner of new reply (if not internal)
            if (!$isInternal && $ticket->user_id !== $userId) {
                $this->sendReplyNotification($ticket, $reply);
            }

            return $reply;
        });
    }

    /**
     * Assign ticket to an agent.
     *
     * @param Ticket $ticket
     * @param int|null $agentId
     * @return Ticket
     */
    public function assignToAgent(Ticket $ticket, ?int $agentId): Ticket
    {
        $ticket->assignTo($agentId);

        // Notify the assigned agent
        if ($agentId) {
            $agent = User::find($agentId);
            if ($agent && $agent->email) {
                Mail::to($agent->email)
                    ->queue(new TicketNotificationMail(
                        $ticket,
                        'Ticket Assigned',
                        "A new ticket has been assigned to you: {$ticket->title}"
                    ));
            }
        }

        return $ticket->fresh();
    }

    /**
     * Get tickets for a user.
     *
     * @param int $userId
     * @param array $filters
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function getUserTickets(int $userId, array $filters = [])
    {
        $query = Ticket::where('user_id', $userId)
            ->with(['user', 'assignee', 'replies']);

        return $this->applyFilters($query, $filters);
    }

    /**
     * Get all tickets with filters (for admins).
     *
     * @param array $filters
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function getAllTickets(array $filters = [])
    {
        $query = Ticket::with(['user', 'assignee', 'replies']);

        return $this->applyFilters($query, $filters);
    }

    /**
     * Get assigned tickets for an agent.
     *
     * @param int $agentId
     * @param array $filters
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function getAgentTickets(int $agentId, array $filters = [])
    {
        $query = Ticket::where('assigned_to', $agentId)
            ->with(['user', 'assignee', 'replies']);

        return $this->applyFilters($query, $filters);
    }

    /**
     * Get ticket statistics.
     *
     * @return array
     */
    public function getStatistics(): array
    {
        return [
            'total' => Ticket::count(),
            'pending' => Ticket::pending()->count(),
            'in_progress' => Ticket::inProgress()->count(),
            'resolved' => Ticket::resolved()->count(),
            'closed' => Ticket::closed()->count(),
            'high_priority' => Ticket::highPriority()->count(),
            'today' => Ticket::whereDate('created_at', today())->count(),
            'this_week' => Ticket::whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()])->count(),
            'this_month' => Ticket::whereMonth('created_at', now()->month)->count(),
        ];
    }

    /**
     * Get tickets by status for dashboard.
     *
     * @param string $status
     * @param int $limit
     * @return Collection
     */
    public function getTicketsByStatus(string $status, int $limit = 10): Collection
    {
        return Ticket::where('status', $status)
            ->with(['user', 'assignee'])
            ->latest()
            ->limit($limit)
            ->get();
    }

    /**
     * Close a ticket.
     *
     * @param Ticket $ticket
     * @param int|null $userId
     * @param string|null $reason
     * @return Ticket
     */
    public function closeTicket(Ticket $ticket, ?int $userId = null, ?string $reason = null): Ticket
    {
        $ticket->close();

        // Add system note about closure
        if ($userId && $reason) {
            TicketReply::create([
                'ticket_id' => $ticket->id,
                'user_id' => $userId,
                'message' => "Ticket closed. Reason: {$reason}",
                'is_internal' => true,
            ]);
        }

        // Notify ticket owner
        $this->sendStatusChangeNotification($ticket, Ticket::STATUS_CLOSED);

        return $ticket->fresh();
    }

    /**
     * Reopen a ticket.
     *
     * @param Ticket $ticket
     * @param int $userId
     * @param string|null $reason
     * @return Ticket
     */
    public function reopenTicket(Ticket $ticket, int $userId, ?string $reason = null): Ticket
    {
        $ticket->reopen();

        // Add note about reopening
        $message = $reason 
            ? "Ticket reopened. Reason: {$reason}" 
            : "Ticket reopened.";
        
        TicketReply::create([
            'ticket_id' => $ticket->id,
            'user_id' => $userId,
            'message' => $message,
            'is_internal' => true,
        ]);

        // Notify administrators
        $this->notifyAdministrators($ticket, 'Ticket Reopened');

        return $ticket->fresh();
    }

    /**
     * Delete a ticket.
     *
     * @param Ticket $ticket
     * @return bool
     */
    public function deleteTicket(Ticket $ticket): bool
    {
        return $ticket->delete();
    }

    /**
     * Apply filters to ticket query.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param array $filters
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    protected function applyFilters($query, array $filters)
    {
        if (!empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (!empty($filters['priority'])) {
            $query->where('priority', $filters['priority']);
        }

        if (!empty($filters['category'])) {
            $query->where('category', $filters['category']);
        }

        if (!empty($filters['search'])) {
            $query->search($filters['search']);
        }

        if (!empty($filters['date_from'])) {
            $query->whereDate('created_at', '>=', $filters['date_from']);
        }

        if (!empty($filters['date_to'])) {
            $query->whereDate('created_at', '<=', $filters['date_to']);
        }

        if (!empty($filters['assigned_to'])) {
            $query->where('assigned_to', $filters['assigned_to']);
        }

        $sortBy = $filters['sort_by'] ?? 'last_activity_at';
        $sortOrder = $filters['sort_order'] ?? 'desc';

        return $query->orderBy($sortBy, $sortOrder)
            ->paginate($filters['per_page'] ?? 15);
    }

    /**
     * Notify administrators about new ticket.
     *
     * @param Ticket $ticket
     * @param string|null $subject
     */
    protected function notifyAdministrators(Ticket $ticket, ?string $subject = null): void
    {
        $administrators = User::whereHas('roles', function ($query) {
            $query->whereIn('name', ['Super Manager', 'Manager']);
        })->get();

        foreach ($administrators as $admin) {
            if ($admin->email) {
                try {
                    Mail::to($admin->email)
                        ->queue(new TicketNotificationMail(
                            $ticket,
                            $subject ?? 'New Support Ticket',
                            "A new ticket has been created: {$ticket->title}"
                        ));
                } catch (\Exception $e) {
                    Log::warning('Failed to queue ticket notification for admin', [
                        'admin_id' => $admin->id,
                        'ticket_id' => $ticket->id,
                        'error' => $e->getMessage(),
                    ]);
                }
            }
        }
    }

    protected function sendStatusChangeNotification(Ticket $ticket, string $previousStatus): void
    {
        $user = $ticket->user;

        if ($user && $user->email) {
            try {
                Mail::to($user->email)
                    ->queue(new TicketStatusMail($ticket, $previousStatus));
            } catch (\Exception $e) {
                Log::warning('Failed to queue ticket status notification', [
                    'user_id' => $user->id,
                    'ticket_id' => $ticket->id,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

    protected function sendReplyNotification(Ticket $ticket, TicketReply $reply): void
    {
        $user = $ticket->user;

        if ($user && $user->email) {
            try {
                Mail::to($user->email)
                    ->queue(new TicketNotificationMail(
                        $ticket,
                        'New Reply to Your Ticket',
                        "Your ticket '{$ticket->title}' has a new reply."
                    ));
            } catch (\Exception $e) {
                Log::warning('Failed to queue ticket reply notification', [
                    'user_id' => $user->id,
                    'ticket_id' => $ticket->id,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

    /**
     * Bulk update ticket statuses.
     *
     * @param array $ticketIds
     * @param string $status
     * @return int Number of tickets updated
     */
    public function bulkUpdateStatus(array $ticketIds, string $status): int
    {
        return Ticket::whereIn('id', $ticketIds)
            ->update([
                'status' => $status,
                'last_activity_at' => now(),
            ]);
    }

    /**
     * Get unique categories from tickets.
     *
     * @return Collection
     */
    public function getCategories(): Collection
    {
        return Ticket::whereNotNull('category')
            ->distinct()
            ->pluck('category');
    }
}
