<?php
namespace App\Controllers;

use App\Core\Auth;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Request;
use App\Core\Session;
use App\Services\AttachmentService;
use App\Services\TicketService;

class TicketController extends Controller
{
    // ------------------------------------------------------------------
    // List views (BRD 4.1: All Tickets / My Queue / Unassigned / Escalated)
    // ------------------------------------------------------------------
    public function index(): void { $this->renderList('All Tickets', '1=1'); }
    public function my(): void { $this->renderList('My Queue', 't.assignee_id = :me', ['me' => Auth::id()], 't.resolution_due_at ASC'); }
    public function unassigned(): void { $this->renderList('Unassigned', 't.assignee_id IS NULL AND t.status NOT IN (\'CLOSED\',\'REJECTED\')'); }
    public function escalated(): void { $this->renderList("Escalated", "t.status = 'ESCALATED'"); }

    private function renderList(string $title, string $baseWhere, array $baseParams = [], string $defaultOrder = 't.created_at DESC'): void
    {
        $status = Request::query('status', '');
        $priority = Request::query('priority', '');
        $queue = Request::query('queue', '');
        $search = trim((string)Request::query('q', ''));

        $where = [$baseWhere];
        $params = $baseParams;
        if ($status !== '') { $where[] = 't.status = :status'; $params['status'] = $status; }
        if ($priority !== '') { $where[] = 't.priority_id = :priority'; $params['priority'] = $priority; }
        if ($queue !== '') { $where[] = 't.queue_id = :queue'; $params['queue'] = $queue; }
        if ($search !== '') {
            $where[] = '(t.ticket_no LIKE :q OR t.subject LIKE :q OR c.name LIKE :q)';
            $params['q'] = '%' . $search . '%';
        }
        if (str_contains($baseWhere, '1=1') && $status === '') {
            // Default "All tickets" view hides closed/rejected unless explicitly filtered.
            $where[] = "t.status NOT IN ('CLOSED','REJECTED')";
        }

        $sql = "SELECT t.*, c.name AS customer_name, p.code AS p_code, p.name AS p_name, p.color AS p_color,
                       u.name AS assignee_name, q.name AS queue_name
                FROM tickets t
                JOIN customers c ON c.id = t.customer_id
                JOIN priorities p ON p.id = t.priority_id
                LEFT JOIN users u ON u.id = t.assignee_id
                LEFT JOIN queues q ON q.id = t.queue_id
                WHERE " . implode(' AND ', $where) . "
                ORDER BY {$defaultOrder} LIMIT 200";

        $tickets = Database::all($sql, $params);
        $priorities = Database::all('SELECT * FROM priorities WHERE is_active = 1 ORDER BY sort_order');
        $queues = Database::all('SELECT * FROM queues WHERE is_active = 1 ORDER BY name');
        $statuses = ['NEW', 'ASSIGNED', 'IN_PROGRESS', 'PENDING_CUSTOMER', 'PENDING_THIRD_PARTY', 'ESCALATED', 'RESOLVED', 'CLOSED', 'REOPENED', 'REJECTED'];
        $view = Request::query('view', 'list') === 'board' ? 'board' : 'list';

        $this->render('tickets/index', [
            'pageTitle' => $title,
            'pageSubtitle' => count($tickets) . ' ticket(s)',
            'tickets' => $tickets, 'priorities' => $priorities, 'queues' => $queues, 'statuses' => $statuses,
            'filters' => compact('status', 'priority', 'queue', 'search'), 'view' => $view,
        ]);
    }

    // ------------------------------------------------------------------
    // Create (agent-logged: telephone / walk-in / email / internal)
    // ------------------------------------------------------------------
    public function createForm(): void
    {
        $this->render('tickets/create', [
            'pageTitle' => 'Log a new ticket',
            'pageSubtitle' => 'On behalf of a customer contact — telephone, walk-in, email or internal',
            'contacts' => Database::all(
                "SELECT ct.id, ct.name, ct.email, c.name AS customer_name FROM contacts ct
                 JOIN customers c ON c.id = ct.customer_id WHERE ct.is_active = 1 ORDER BY c.name, ct.name"
            ),
            'products' => Database::all('SELECT * FROM products WHERE is_active = 1 ORDER BY name'),
            'categories' => Database::all('SELECT * FROM categories WHERE is_active = 1 ORDER BY parent_id IS NULL DESC, name'),
            'priorities' => Database::all('SELECT * FROM priorities WHERE is_active = 1 ORDER BY sort_order'),
        ]);
    }

    public function store(): void
    {
        $this->requireCsrf();
        try {
            $ticket = TicketService::create([
                'contact_id' => (int)Request::post('contact_id'),
                'product_id' => (int)Request::post('product_id'),
                'subject' => trim((string)Request::post('subject')),
                'description' => trim((string)Request::post('description')),
                'category_id' => (int)Request::post('category_id'),
                'sub_category_id' => Request::post('sub_category_id') ?: null,
                'priority_id' => (int)Request::post('priority_id'),
                'severity' => Request::post('severity', 'Minor impact'),
                'channel' => Request::post('channel', 'Telephone'),
            ], Auth::id(), null);

            $this->attachFilesToMessage('attachments', $ticket['initial_message_id'], (int)$ticket['id'], Auth::id(), null);

            Session::flash('success', "Ticket {$ticket['ticket_no']} created.");
            $this->redirect('/tickets/' . $ticket['id']);
        } catch (\Throwable $e) {
            Session::flash('error', $e->getMessage());
            $this->redirect('/tickets/new');
        }
    }

    // ------------------------------------------------------------------
    // Detail
    // ------------------------------------------------------------------
    public function show(string $id): void
    {
        $ticket = $this->findOr404((int)$id);

        $messages = Database::all(
            "SELECT m.*, u.name AS user_name, u.role_id, r.code AS role_code, c.name AS contact_name
             FROM ticket_messages m
             LEFT JOIN users u ON u.id = m.user_id LEFT JOIN roles r ON r.id = u.role_id
             LEFT JOIN contacts c ON c.id = m.contact_id
             WHERE m.ticket_id = :t ORDER BY m.created_at ASC",
            ['t' => $ticket['id']]
        );
        $attachments = Database::all(
            "SELECT a.*, u.name AS uploaded_by_user_name, c.name AS uploaded_by_contact_name
             FROM ticket_attachments a
             LEFT JOIN users u ON u.id = a.uploaded_by_user_id
             LEFT JOIN contacts c ON c.id = a.uploaded_by_contact_id
             WHERE a.ticket_id = :t ORDER BY a.created_at", ['t' => $ticket['id']]
        );
        $attachmentsByMsg = [];
        foreach ($attachments as $a) { $attachmentsByMsg[$a['ticket_message_id'] ?? 0][] = $a; }

        $auditLog = Database::all(
            "SELECT a.*, u.name AS user_name FROM ticket_audit_log a LEFT JOIN users u ON u.id = a.user_id
             WHERE a.ticket_id = :t ORDER BY a.created_at DESC", ['t' => $ticket['id']]
        );
        $timeLogs = Database::all(
            "SELECT tl.*, u.name AS user_name FROM time_logs tl JOIN users u ON u.id = tl.user_id
             WHERE tl.ticket_id = :t ORDER BY tl.log_date DESC", ['t' => $ticket['id']]
        );

        $customer = Database::one('SELECT * FROM customers WHERE id = :id', ['id' => $ticket['customer_id']]);
        $contact = Database::one('SELECT * FROM contacts WHERE id = :id', ['id' => $ticket['contact_id']]);
        $contract = $ticket['contract_id'] ? Database::one('SELECT * FROM contracts WHERE id = :id', ['id' => $ticket['contract_id']]) : null;
        $priority = Database::one('SELECT * FROM priorities WHERE id = :id', ['id' => $ticket['priority_id']]);
        $product = Database::one('SELECT * FROM products WHERE id = :id', ['id' => $ticket['product_id']]);
        $category = Database::one('SELECT * FROM categories WHERE id = :id', ['id' => $ticket['category_id']]);
        $assignee = $ticket['assignee_id'] ? Database::one('SELECT * FROM users WHERE id = :id', ['id' => $ticket['assignee_id']]) : null;

        $slaTarget = $ticket['sla_policy_id'] ? Database::one('SELECT * FROM sla_targets WHERE sla_policy_id = :p AND priority_id = :pr', ['p' => $ticket['sla_policy_id'], 'pr' => $ticket['priority_id']]) : null;
        $slaPolicy = $ticket['sla_policy_id'] ? Database::one('SELECT * FROM sla_policies WHERE id = :id', ['id' => $ticket['sla_policy_id']]) : null;

        $agents = Database::all("SELECT u.* FROM users u WHERE u.is_active = 1 ORDER BY u.name");
        $categories = Database::all('SELECT * FROM categories WHERE is_active = 1 ORDER BY name');
        $priorities = Database::all('SELECT * FROM priorities WHERE is_active = 1 ORDER BY sort_order');
        $canned = Database::all('SELECT * FROM canned_responses WHERE is_active = 1 ORDER BY title');

        $hasInternalNotes = (bool)Database::scalar("SELECT COUNT(*) FROM ticket_messages WHERE ticket_id = :t AND type = 'internal_note'", ['t' => $ticket['id']]);

        $this->render('tickets/show', [
            'pageTitle' => $ticket['ticket_no'],
            'pageSubtitle' => $ticket['subject'],
            'ticket' => $ticket, 'messages' => $messages, 'attachmentsByMsg' => $attachmentsByMsg, 'attachments' => $attachments,
            'auditLog' => $auditLog, 'timeLogs' => $timeLogs,
            'customer' => $customer, 'contact' => $contact, 'contract' => $contract,
            'priority' => $priority, 'product' => $product, 'category' => $category, 'assignee' => $assignee,
            'slaTarget' => $slaTarget, 'slaPolicy' => $slaPolicy,
            'agents' => $agents, 'categories' => $categories, 'priorities' => $priorities, 'canned' => $canned,
            'hasInternalNotes' => $hasInternalNotes,
            'allowedTransitions' => TicketService::allowedTransitions($ticket['status']),
        ]);
    }

    // ------------------------------------------------------------------
    // Actions
    // ------------------------------------------------------------------
    public function reply(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        $body = trim((string)Request::post('body'));
        if ($body === '') { Session::flash('error', 'Reply cannot be empty.'); $this->redirect('/tickets/' . $id); return; }

        try {
            $messageId = TicketService::addMessage($ticket, 'public_reply', $body, Auth::id(), null, Request::post('kb_article_id') ?: null, (bool)Request::post('from_canned'));
            $this->attachFilesToMessage('attachments', $messageId, (int)$ticket['id'], Auth::id(), null);
            Session::flash('success', 'Public reply sent.');
        } catch (\Throwable $e) {
            Session::flash('error', $e->getMessage());
        }
        $this->redirect('/tickets/' . $id);
    }

    public function note(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        $body = trim((string)Request::post('body'));
        if ($body === '') { Session::flash('error', 'Note cannot be empty.'); $this->redirect('/tickets/' . $id); return; }

        try {
            $messageId = TicketService::addMessage($ticket, 'internal_note', $body, Auth::id(), null);
            $this->attachFilesToMessage('attachments', $messageId, (int)$ticket['id'], Auth::id(), null);
            Session::flash('success', 'Internal note added.');
        } catch (\Throwable $e) {
            Session::flash('error', $e->getMessage());
        }
        $this->redirect('/tickets/' . $id);
    }

    public function changeStatus(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        $newStatus = (string)Request::post('status');
        $reason = Request::post('reason') ?: null;

        if (in_array($newStatus, ['RESOLVED', 'CLOSED'], true) && !Auth::can('ticket.resolve_close')) {
            Session::flash('error', 'You do not have permission to resolve or close tickets.');
            $this->redirect('/tickets/' . $id);
            return;
        }

        try {
            TicketService::changeStatus($ticket, $newStatus, Auth::id(), $reason, [
                'resolution_code' => Request::post('resolution_code'),
                'root_cause' => Request::post('root_cause'),
                'justification' => Request::post('justification'),
            ]);
            Session::flash('success', 'Ticket status updated to ' . status_label($newStatus) . '.');
        } catch (\Throwable $e) {
            Session::flash('error', $e->getMessage());
        }
        $this->redirect('/tickets/' . $id);
    }

    /**
     * AJAX status change for the Kanban board's drag-and-drop. Deliberately narrower than
     * changeStatus(): only statuses that need no extra structured input (resolution code,
     * root cause, rejection reason) are reachable this way — RESOLVED/CLOSED/REJECTED still
     * require opening the ticket, where those fields are actually collected. ESCALATED goes
     * through the dedicated escalate() business rule (it must record a reason and bump the
     * escalation level), not a bare status write.
     */
    public function kanbanMove(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        $to = (string)Request::post('to_status');
        $draggable = ['NEW', 'ASSIGNED', 'IN_PROGRESS', 'PENDING_CUSTOMER', 'PENDING_THIRD_PARTY'];

        try {
            if ($to === 'ESCALATED') {
                if (!Auth::can('ticket.escalate')) { $this->json(['ok' => false, 'error' => 'You do not have permission to escalate tickets.'], 403); return; }
                $reason = trim((string)Request::post('reason'));
                if ($reason === '') { $this->json(['ok' => false, 'error' => 'A reason is required to escalate.'], 422); return; }
                TicketService::escalate($ticket, Auth::id(), $reason);
            } elseif (in_array($to, $draggable, true)) {
                TicketService::changeStatus($ticket, $to, Auth::id(), 'Moved on the Kanban board');
            } else {
                $this->json(['ok' => false, 'error' => 'Open the ticket to move it to ' . status_label($to) . ' — that status needs more detail than a drag can capture.'], 422);
                return;
            }
            $this->json(['ok' => true]);
        } catch (\Throwable $e) {
            $this->json(['ok' => false, 'error' => $e->getMessage()], 422);
        }
    }

    public function assign(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        if (!Auth::can('ticket.assign')) { Session::flash('error', 'You do not have permission to assign tickets.'); $this->redirect('/tickets/' . $id); return; }

        TicketService::assign($ticket, (int)Request::post('assignee_id'), Auth::id(), Request::post('reason'));
        Session::flash('success', 'Ticket reassigned.');
        $this->redirect('/tickets/' . $id);
    }

    public function claim(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        TicketService::assign($ticket, Auth::id(), Auth::id(), 'Self-claimed from queue');
        Session::flash('success', 'Ticket claimed.');
        $this->redirect('/tickets/' . $id);
    }

    public function escalate(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        if (!Auth::can('ticket.escalate')) { Session::flash('error', 'You do not have permission to escalate tickets.'); $this->redirect('/tickets/' . $id); return; }
        $reason = trim((string)Request::post('reason'));
        if ($reason === '') { Session::flash('error', 'A reason is required to escalate.'); $this->redirect('/tickets/' . $id); return; }

        TicketService::escalate($ticket, Auth::id(), $reason);
        Session::flash('success', 'Ticket escalated.');
        $this->redirect('/tickets/' . $id);
    }

    public function classify(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        $reason = trim((string)Request::post('reason'));
        if ($reason === '') { Session::flash('error', 'A reason is required when changing classification.'); $this->redirect('/tickets/' . $id); return; }

        TicketService::updateClassification($ticket, [
            'priority_id' => Request::post('priority_id'),
            'category_id' => Request::post('category_id'),
            'severity' => Request::post('severity'),
        ], Auth::id(), $reason);
        Session::flash('success', 'Classification updated.');
        $this->redirect('/tickets/' . $id);
    }

    public function uploadAttachment(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        $file = Request::file('attachment');
        if (!$file || $file['error'] === UPLOAD_ERR_NO_FILE) { $this->redirect('/tickets/' . $id); return; }

        try {
            $meta = AttachmentService::store($file, (int)$ticket['id']);
            Database::insert('ticket_attachments', array_merge($meta, [
                'ticket_id' => $ticket['id'], 'uploaded_by_user_id' => Auth::id(),
            ]));
            TicketService::audit((int)$ticket['id'], Auth::id(), 'attachment_added', null, null, $meta['original_name']);
            Session::flash('success', 'Attachment uploaded.');
        } catch (\Throwable $e) {
            Session::flash('error', $e->getMessage());
        }
        $this->redirect('/tickets/' . $id);
    }

    public function downloadAttachment(string $id, string $attId): void
    {
        $att = Database::one('SELECT * FROM ticket_attachments WHERE id = :id AND ticket_id = :t', ['id' => $attId, 't' => $id]);
        if (!$att) { $this->notFound(); return; }
        AttachmentService::streamDownload($att);
    }

    public function logTime(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        // Anyone can log effort; only permitted roles can flag it billable (BRD 9: L1/L2/L3 log
        // effort but don't decide billability). Silently ignored rather than rejected outright —
        // the effort entry itself is still valid and worth keeping.
        $isBillable = (bool)Request::post('is_billable') && Auth::can('billing.mark_billable');
        TicketService::logTime(
            (int)$ticket['id'], Auth::id(),
            Request::post('log_date', date('Y-m-d')),
            (int)Request::post('duration_minutes'),
            Request::post('activity_type', 'Investigation'),
            Request::post('description'),
            $isBillable,
            Request::post('billable_reason')
        );
        Session::flash('success', 'Effort logged.');
        $this->redirect('/tickets/' . $id);
    }

    public function linkRecord(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        Database::update('tickets', [
            'linked_record_type' => Request::post('linked_record_type'),
            'linked_record_ref' => Request::post('linked_record_ref'),
            'linked_record_status' => 'Open',
        ], 'id = :id', ['id' => $ticket['id']]);
        TicketService::audit((int)$ticket['id'], Auth::id(), 'linked_record', 'linked_record_ref', null, Request::post('linked_record_ref'));
        Session::flash('success', 'Linked record saved.');
        $this->redirect('/tickets/' . $id);
    }

    public function watch(string $id): void
    {
        $this->requireCsrf();
        $ticket = $this->findOr404((int)$id);
        try {
            Database::insert('watchers', ['ticket_id' => $ticket['id'], 'user_id' => Auth::id()]);
            Session::flash('success', 'You are now watching this ticket.');
        } catch (\Throwable $e) {
            Session::flash('info', 'You already watch this ticket.');
        }
        $this->redirect('/tickets/' . $id);
    }

    private function findOr404(int $id): array
    {
        $ticket = Database::one('SELECT * FROM tickets WHERE id = :id', ['id' => $id]);
        if (!$ticket) { $this->notFound(); }
        return $ticket;
    }

    /** Stores every uploaded file under $fieldName and links each to a specific ticket message. */
    private function attachFilesToMessage(string $fieldName, ?int $messageId, int $ticketId, ?int $userId, ?int $contactId): void
    {
        foreach (Request::files($fieldName) as $file) {
            $meta = AttachmentService::store($file, $ticketId);
            Database::insert('ticket_attachments', array_merge($meta, [
                'ticket_id' => $ticketId, 'ticket_message_id' => $messageId,
                'uploaded_by_user_id' => $userId, 'uploaded_by_contact_id' => $contactId,
            ]));
        }
    }
}
