<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Task;
use App\Models\Notification;
use App\Models\Database;

class TaskService
{
    private Task $taskModel;

    private const VALID_STATUSES = ['pendiente', 'completada', 'cancelada'];

    private const ALLOWED_MIME = [
        'application/pdf',
        'image/jpeg',
        'image/png',
        'image/webp',
        'image/gif',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'application/vnd.ms-excel',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'application/vnd.ms-powerpoint',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'text/plain',
    ];

    private const MAX_FILE_SIZE = 20 * 1024 * 1024;

    public function __construct()
    {
        $this->taskModel = new Task();
    }

    public function createTask(array $data, int $userId): array
    {
        $type = $this->normalizeType($data['type'] ?? null);
        if (empty($data['scheduled_date'])) {
            throw new \RuntimeException('La fecha programada es obligatoria', 422);
        }

        $taskId = $this->taskModel->createTask([
            'lead_id' => !empty($data['lead_id']) ? (int) $data['lead_id'] : null,
            'type' => $type,
            'scheduled_date' => $data['scheduled_date'],
            'scheduled_time' => $data['scheduled_time'] ?? null,
            'notes' => $data['notes'] ?? null,
            'farm_lat' => isset($data['farm_lat']) ? (float) $data['farm_lat'] : null,
            'farm_lng' => isset($data['farm_lng']) ? (float) $data['farm_lng'] : null,
            'assigned_to' => (int) ($data['assigned_to'] ?? $userId),
            'created_by' => $userId,
        ]);

        $assignedTo = (int) ($data['assigned_to'] ?? $userId);
        $typeLabel = ['llamada'=>'Llamar cliente','reunion'=>'Visita tecnica','correo'=>'Enviar cotizacion','whatsapp'=>'WhatsApp','seguimiento'=>'Seguimiento','otro'=>'Otro'];
        $notificationModel = new Notification();
        $notificationModel->createNotification([
            'user_id' => $assignedTo,
            'type' => 'nueva_tarea',
            'title' => 'Nueva tarea: ' . ($typeLabel[$type] ?? $type),
            'message' => "Tarea programada para el {$data['scheduled_date']}" . (!empty($data['scheduled_time']) ? " a las {$data['scheduled_time']}" : ''),
            'reference_type' => 'task',
            'reference_id' => $taskId,
        ]);

        return $this->getTask($taskId);
    }

    public function getTasksByLead(int $leadId): array
    {
        return $this->taskModel->getByLeadId($leadId);
    }

    public function getMyTasks(int $userId, array $filters = [], int $page = 1, int $perPage = 50): array
    {
        return $this->taskModel->getByUserId($userId, $filters, $page, $perPage);
    }

    public function getAllTasks(array $filters = [], int $page = 1, int $perPage = 50): array
    {
        return $this->taskModel->getAll($filters, $page, $perPage);
    }

    public function updateTask(int $id, array $data): array
    {
        $task = $this->taskModel->findById($id);
        if (!$task) {
            throw new \RuntimeException('Tarea no encontrada', 404);
        }

        $updateData = [];
        foreach (['type', 'status', 'scheduled_date', 'scheduled_time', 'notes', 'assigned_to', 'farm_lat', 'farm_lng'] as $field) {
            if (array_key_exists($field, $data)) {
                $updateData[$field] = $data[$field];
            }
        }

        if (array_key_exists('type', $updateData)) {
            $updateData['type'] = $this->normalizeType($updateData['type']);
        }

        if (array_key_exists('status', $updateData)) {
            if (!in_array($updateData['status'], self::VALID_STATUSES)) {
                throw new \RuntimeException('Estado no valido', 422);
            }
            if ($updateData['status'] === 'completada') {
                $updateData['completed_at'] = date('Y-m-d H:i:s');
            } elseif ($updateData['status'] === 'pendiente') {
                $updateData['completed_at'] = null;
            }
        }

        if (!empty($updateData)) {
            $this->taskModel->update($id, $updateData);
        }

        return $this->getTask($id);
    }

    public function changeStatus(int $id, string $status): array
    {
        $task = $this->taskModel->findById($id);
        if (!$task) {
            throw new \RuntimeException('Tarea no encontrada', 404);
        }

        if (!in_array($status, self::VALID_STATUSES)) {
            throw new \RuntimeException('Estado no valido', 422);
        }

        $updateData = ['status' => $status];
        if ($status === 'completada') {
            $updateData['completed_at'] = date('Y-m-d H:i:s');
        } else {
            $updateData['completed_at'] = null;
        }

        $this->taskModel->update($id, $updateData);

        return $this->getTask($id);
    }

    public function deleteTask(int $id, int $userId, string $role = ''): void
    {
        $task = $this->taskModel->findById($id);
        if (!$task) {
            throw new \RuntimeException('Tarea no encontrada', 404);
        }

        if ($role !== 'super_admin' && (int) $task['created_by'] !== $userId && (int) ($task['assigned_to'] ?? 0) !== $userId) {
            throw new \RuntimeException('No tienes permiso para eliminar esta tarea', 403);
        }

        $this->deleteAllAttachmentsFiles($id);
        $this->taskModel->delete($id);
    }

    public function getTaskTypes(): array
    {
        $db = Database::connect();
        return $db->query("SELECT DISTINCT type FROM tasks ORDER BY type")->fetchAll();
    }

    public function renameTaskType(string $oldType, string $newType): void
    {
        $newType = $this->normalizeType($newType);
        if (empty($oldType)) {
            throw new \RuntimeException('El tipo original es obligatorio', 422);
        }
        $db = Database::connect();
        $stmt = $db->prepare("UPDATE tasks SET type = :new WHERE type = :old");
        $stmt->execute(['new' => $newType, 'old' => $oldType]);
    }

    public function deleteTaskType(string $type): void
    {
        if (empty($type)) {
            throw new \RuntimeException('El tipo es obligatorio', 422);
        }
        $db = Database::connect();
        $stmt = $db->prepare("DELETE FROM tasks WHERE type = :type");
        $stmt->execute(['type' => $type]);
    }

    private function getTask(int $id): array
    {
        return $this->getTaskById($id);
    }

    public function getTaskById(int $id): array
    {
        $task = $this->taskModel->findById($id);
        if (!$task) {
            throw new \RuntimeException('Tarea no encontrada', 404);
        }

        $task['attachments'] = $this->getAttachments($id);
        return $task;
    }

    /* ========== ATTACHMENT METHODS ========== */

    public function getAttachments(int $taskId): array
    {
        $db = Database::connect();
        $stmt = $db->prepare(
            "SELECT id, file_path, original_name, created_at
             FROM task_attachments
             WHERE task_id = :task_id
             ORDER BY created_at ASC"
        );
        $stmt->execute(['task_id' => $taskId]);
        return $stmt->fetchAll();
    }

    public function storeAttachments(int $taskId, array $files): array
    {
        $saved = [];
        $fileArray = $this->normalizeFilesArray($files);

        foreach ($fileArray as $file) {
            if ($file['error'] !== UPLOAD_ERR_OK) {
                continue;
            }
            if ($file['size'] > self::MAX_FILE_SIZE) {
                throw new \RuntimeException("El archivo {$file['name']} no puede superar 20MB", 422);
            }

            $detectedMime = (new \finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
            if (!in_array($detectedMime, self::ALLOWED_MIME, true)) {
                throw new \RuntimeException("Tipo de archivo no permitido: {$file['name']}. Solo se permiten: PDF, imagenes, Word, Excel, PowerPoint y texto", 422);
            }

            $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
            $filename = uniqid('task_') . '.' . $ext;
            $subDir = STORAGE_PATH . '/uploads/tasks';

            if (!is_dir($subDir)) {
                mkdir($subDir, 0755, true);
            }

            $destPath = $subDir . '/' . $filename;
            if (!move_uploaded_file($file['tmp_name'], $destPath)) {
                throw new \RuntimeException('Error al guardar el archivo', 500);
            }

            $db = Database::connect();
            $stmt = $db->prepare(
                "INSERT INTO task_attachments (task_id, file_path, original_name) VALUES (:task_id, :file_path, :original_name)"
            );
            $stmt->execute([
                'task_id' => $taskId,
                'file_path' => $filename,
                'original_name' => $file['name'],
            ]);

            $saved[] = [
                'id' => (int) $db->lastInsertId(),
                'file_path' => $filename,
                'original_name' => $file['name'],
            ];
        }

        return $saved;
    }

    public function deleteAttachmentFile(int $attachmentId): void
    {
        $db = Database::connect();
        $stmt = $db->prepare("SELECT * FROM task_attachments WHERE id = :id");
        $stmt->execute(['id' => $attachmentId]);
        $attachment = $stmt->fetch();

        if (!$attachment) {
            throw new \RuntimeException('Archivo adjunto no encontrado', 404);
        }

        $path = STORAGE_PATH . '/uploads/tasks/' . basename($attachment['file_path']);
        if (file_exists($path)) {
            unlink($path);
        }

        $stmt = $db->prepare("DELETE FROM task_attachments WHERE id = :id");
        $stmt->execute(['id' => $attachmentId]);
    }

    public function deleteAllAttachmentsFiles(int $taskId): void
    {
        $attachments = $this->getAttachments($taskId);
        foreach ($attachments as $attachment) {
            $path = STORAGE_PATH . '/uploads/tasks/' . basename($attachment['file_path']);
            if (file_exists($path)) {
                unlink($path);
            }
        }
    }

    private function normalizeFilesArray(array $files): array
    {
        $result = [];
        if (!isset($files['name'])) {
            return $result;
        }
        if (is_string($files['name'])) {
            return [$files];
        }
        $count = count($files['name']);
        for ($i = 0; $i < $count; $i++) {
            if ($files['error'][$i] !== UPLOAD_ERR_NO_FILE) {
                $result[] = [
                    'name' => $files['name'][$i],
                    'type' => $files['type'][$i],
                    'tmp_name' => $files['tmp_name'][$i],
                    'error' => $files['error'][$i],
                    'size' => $files['size'][$i],
                ];
            }
        }
        return $result;
    }

    private function normalizeType(mixed $type): string
    {
        $normalized = trim((string) ($type ?? ''));
        if ($normalized === '' || $normalized === '__new__') {
            throw new \RuntimeException('El tipo de tarea es obligatorio', 422);
        }
        if (strlen($normalized) > 100) {
            throw new \RuntimeException('El tipo de tarea no puede superar 100 caracteres', 422);
        }
        return $normalized;
    }
}
