<?php
/**
 * Task Importer for taskmassivas plugin
 * Processes Excel and CSV files for bulk task creation
 *
 * Formato simplificado del template:
 * año | cliente | codigo del proyecto - sie | tipo | descripción | enero | febrero | ... | diciembre
 *
 * Cada fila puede generar múltiples tareas (una por cada mes que tenga fecha)
 */

namespace dokuwiki\plugin\taskmassivas\lib;

class TaskImporter {

    protected $helper;
    protected $errors = [];
    protected $warnings = [];
    protected $stats = [
        'total_rows' => 0,
        'total_tasks' => 0,
        'valid' => 0,
        'errors' => 0,
        'created' => 0
    ];

    // Nombres de meses en español
    protected $monthNames = [
        'enero' => 1, 'febrero' => 2, 'marzo' => 3, 'abril' => 4,
        'mayo' => 5, 'junio' => 6, 'julio' => 7, 'agosto' => 8,
        'septiembre' => 9, 'octubre' => 10, 'noviembre' => 11, 'diciembre' => 12
    ];

    // Mapeo de columnas principales (incluye variantes con/sin tilde, mayúsculas)
    protected $columnMappings = [
        'year' => ['año', 'Año', 'ano', 'Ano', 'year', 'anio'],
        'client' => ['cliente', 'Cliente', 'client', 'empresa', 'company'],
        'project_code' => ['codigo del proyecto - sie', 'Codigo del proyecto - sie', 'codigo proyecto', 'Codigo proyecto', 'codigo', 'Codigo', 'code', 'proyecto', 'project_code', 'id_proyecto'],
        'type' => ['tipo', 'Tipo', 'type', 'tipo_tarea', 'task_type'],
        'content' => ['descripcion de la tarea', 'Descripcion de la tarea', 'descripcion', 'Descripcion', 'description', 'contenido', 'content', 'tarea', 'task'],
        'assignee' => ['responsable', 'Responsable', 'asignado', 'Asignado', 'assignee', 'usuario', 'encargado', 'asignado a', 'responsible']
    ];

    public function __construct($helper) {
        $this->helper = $helper;
    }

    /**
     * Import tasks from file
     * @param string $filePath Path to the uploaded file (temp file)
     * @param bool $preview If true, only validate without creating tasks
     * @param string $userId User performing the import
     * @param string $originalName Original filename (to detect extension)
     */
    public function import($filePath, $preview = true, $userId = null, $originalName = null) {
        $this->errors = [];
        $this->warnings = [];
        $this->stats = [
            'total_rows' => 0,
            'total_tasks' => 0,
            'valid' => 0,
            'errors' => 0,
            'created' => 0
        ];

        // Detect file type from original filename (temp files don't have extension)
        $detectFrom = $originalName ?: $filePath;
        $ext = strtolower(pathinfo($detectFrom, PATHINFO_EXTENSION));

        $rawData = [];

        switch ($ext) {
            case 'csv':
                $rawData = $this->readCSV($filePath);
                break;
            case 'xlsx':
                $rawData = $this->readExcel($filePath);
                break;
            default:
                $this->errors[] = 'Formato de archivo no soportado. Use .xlsx o .csv';
                return $this->buildResult([], $preview);
        }

        if (empty($rawData['rows'])) {
            $this->errors[] = 'No se encontraron datos en el archivo';
            return $this->buildResult([], $preview);
        }

        // Process rows and generate tasks
        $tasks = $this->processRows($rawData['header'], $rawData['rows']);

        if (empty($tasks)) {
            if (empty($this->errors)) {
                $this->errors[] = 'No se generaron tareas válidas del archivo';
            }
            return $this->buildResult($tasks, $preview);
        }

        // If not preview, create tasks
        if (!$preview && $this->stats['valid'] > 0) {
            foreach ($tasks as &$task) {
                if ($task['valid']) {
                    // Detección de duplicados: verificar si ya existe una tarea
                    // con mismo proyecto + misma fecha + mismo contenido raw
                    if ($this->isDuplicate($task)) {
                        $task['valid'] = false;
                        $task['errors'][] = 'Tarea duplicada (ya existe)';
                        $this->warnings[] = "Fila {$task['row']}, {$task['month_name']}: Tarea duplicada, omitida";
                        $this->stats['valid']--;
                        $this->stats['errors']++;
                        continue;
                    }

                    $taskId = $this->helper->createTask($task, $userId);
                    if ($taskId) {
                        $task['task_id'] = $taskId;
                        $this->stats['created']++;
                    } else {
                        $task['valid'] = false;
                        $task['errors'][] = 'Error al crear tarea';
                        $this->errors[] = "Fila {$task['row']}: Error al crear tarea para {$task['month_name']}";
                    }
                }
            }
        }

        return $this->buildResult($tasks, $preview);
    }

    /**
     * Process rows and generate tasks (one per month with date)
     */
    protected function processRows($header, $rows) {
        $tasks = [];

        // Map columns
        $columnMap = $this->mapColumns($header);
        $monthColumns = $this->mapMonthColumns($header);

        if (empty($columnMap['content'])) {
            $this->errors[] = 'No se encontró columna de descripción de tarea. Columnas disponibles: ' . implode(', ', $header);
            return $tasks;
        }

        if (empty($monthColumns)) {
            $this->errors[] = 'No se encontraron columnas de meses (enero-diciembre). Columnas disponibles: ' . implode(', ', $header);
            return $tasks;
        }

        foreach ($rows as $rowIndex => $row) {
            $rowNum = $rowIndex + 2; // Row 1 is header
            $this->stats['total_rows']++;

            if (empty(array_filter($row))) continue;

            // Get base task data
            $baseTask = $this->extractBaseTask($row, $columnMap, $rowNum);

            if (empty($baseTask['content'])) {
                $this->errors[] = "Fila $rowNum: Sin descripción de tarea";
                $this->stats['errors']++;
                continue;
            }

            // Find project
            $project = null;
            if (!empty($baseTask['project_code'])) {
                $project = $this->helper->findProjectByCode($baseTask['project_code']);
            }

            if (!$project) {
                $this->errors[] = "Fila $rowNum: Proyecto no encontrado (código: {$baseTask['project_code']})";
                $this->stats['errors']++;
                continue;
            }

            // Validar assignee UNA sola vez por fila (no por cada mes)
            $assigneeValid = true;
            $assigneeWarning = '';
            if (!empty($baseTask['assignee'])) {
                if (!$this->helper->userExists($baseTask['assignee'])) {
                    $assigneeValid = false;
                    $assigneeWarning = "Usuario '{$baseTask['assignee']}' no existe";
                    $this->warnings[] = "Fila $rowNum: $assigneeWarning (se usará usuario actual en todos los meses)";
                }
            }

            // Generate a task for each month with a date
            $monthsWithTasks = 0;
            foreach ($monthColumns as $monthName => $colIndex) {
                $monthNum = $this->monthNames[$monthName];
                $dateValue = isset($row[$colIndex]) ? trim($row[$colIndex]) : '';

                if (empty($dateValue)) continue;

                // Parse the date(s) — puede retornar string, array de strings, o null
                $planDates = $this->parseMonthDate($dateValue, $monthNum, $baseTask['year']);

                if (!$planDates) {
                    $this->warnings[] = "Fila $rowNum, $monthName: Fecha inválida '$dateValue'";
                    continue;
                }

                // Normalizar a array para manejar tanto día único como múltiples
                if (!is_array($planDates)) {
                    $planDates = [$planDates];
                }

                foreach ($planDates as $planDate) {
                    // Format task content with structured table
                    $formattedContent = $this->formatTaskContent([
                        'type' => $baseTask['type'],
                        'description' => $baseTask['content'],
                        'month_name' => ucfirst($monthName),
                        'year' => $baseTask['year'],
                        'plan_date' => $planDate,
                        'client_name' => isset($project['client_name']) ? $project['client_name'] : $baseTask['client'],
                        'client_nit' => isset($project['client_nit']) ? $project['client_nit'] : '',
                        'client_dv' => isset($project['client_dv']) ? $project['client_dv'] : ''
                    ]);

                    // Create task for this date
                    $task = [
                        'row' => $rowNum,
                        'content' => $formattedContent,
                        'content_raw' => $baseTask['content'],
                        'plan_date' => $planDate,
                        'project_id' => $project['id'],
                        'project_title' => $project['title'],
                        'type' => $baseTask['type'],
                        'assignee' => $assigneeValid ? $baseTask['assignee'] : '',
                        'program' => 'PLAN MENSUAL DE TRABAJO',
                        'month_name' => ucfirst($monthName),
                        'month_num' => $monthNum,
                        'client' => $baseTask['client'],
                        'year' => $baseTask['year'],
                        'valid' => true,
                        'errors' => [],
                        'warnings' => $assigneeWarning ? [$assigneeWarning] : []
                    ];

                    $tasks[] = $task;
                    $this->stats['total_tasks']++;
                    $this->stats['valid']++;
                    $monthsWithTasks++;
                } // end foreach $planDates
            }

            if ($monthsWithTasks === 0) {
                $this->warnings[] = "Fila $rowNum: No tiene fechas en ningún mes";
            }
        }

        return $tasks;
    }

    /**
     * Extract base task data from row
     */
    protected function extractBaseTask($row, $columnMap, $rowNum) {
        $task = [
            'year' => date('Y'),
            'client' => '',
            'project_code' => '',
            'type' => 'correction',
            'content' => '',
            'assignee' => ''
        ];

        // Year
        if (isset($columnMap['year']) && isset($row[$columnMap['year']])) {
            $year = trim($row[$columnMap['year']]);
            if (is_numeric($year) && $year > 2000 && $year < 2100) {
                $task['year'] = intval($year);
            }
        }

        // Client
        if (isset($columnMap['client']) && isset($row[$columnMap['client']])) {
            $task['client'] = trim($row[$columnMap['client']]);
        }

        // Project code
        if (isset($columnMap['project_code']) && isset($row[$columnMap['project_code']])) {
            $task['project_code'] = trim($row[$columnMap['project_code']]);
        }

        // Type - accepts Spanish and English
        if (isset($columnMap['type']) && isset($row[$columnMap['type']])) {
            $type = strtolower(trim($row[$columnMap['type']]));

            // Map Spanish to English
            $typeMap = [
                'correctiva' => 'corrective',
                'accion correctiva' => 'corrective',
                'acción correctiva' => 'corrective',
                'preventiva' => 'preventive',
                'programada' => 'program',
                'critica' => 'critical',
                'crítica' => 'critical',
                'importante' => 'preventive',
                'urgente' => 'critical'
            ];

            if (isset($typeMap[$type])) {
                $type = $typeMap[$type];
            }

            $validTypes = ['correction', 'corrective', 'preventive', 'program', 'critical'];
            $task['type'] = in_array($type, $validTypes) ? $type : 'correction';
        }

        // Content/Description
        if (isset($columnMap['content']) && isset($row[$columnMap['content']])) {
            $task['content'] = trim($row[$columnMap['content']]);
        }

        // Assignee/Responsable
        if (isset($columnMap['assignee']) && isset($row[$columnMap['assignee']])) {
            $task['assignee'] = trim($row[$columnMap['assignee']]);
        }

        return $task;
    }

    /**
     * Parse date(s) for a specific month
     * Soporta múltiples días separados por coma: "5,20,28"
     * @return string|array|null  Una fecha, array de fechas, o null
     */
    protected function parseMonthDate($value, $month, $year) {
        $value = trim($value);
        if (empty($value)) return null;

        // Si contiene comas, parsear múltiples días
        if (strpos($value, ',') !== false) {
            $days = array_map('trim', explode(',', $value));
            $dates = [];
            foreach ($days as $dayStr) {
                if (empty($dayStr)) continue;
                $parsed = $this->parseSingleMonthDate($dayStr, $month, $year);
                if ($parsed) {
                    $dates[] = $parsed;
                }
            }
            return !empty($dates) ? $dates : null;
        }

        return $this->parseSingleMonthDate($value, $month, $year);
    }

    /**
     * Parse a single date value for a specific month
     */
    protected function parseSingleMonthDate($value, $month, $year) {
        $value = trim($value);
        if (empty($value)) return null;

        // If it's just a day number (1-31)
        if (preg_match('/^(\d{1,2})$/', $value, $m)) {
            $day = intval($m[1]);
            if ($day >= 1 && $day <= 31) {
                return $this->helper->buildDate($day, $month, $year);
            }
        }

        // Try standard date parsing
        return $this->helper->parseDate($value, $year);
    }

    /**
     * Map header columns to task fields
     */
    protected function mapColumns($header) {
        $map = [];

        foreach ($this->columnMappings as $field => $aliases) {
            // First pass: exact match
            foreach ($header as $index => $colName) {
                $normalizedCol = $this->normalizeColumnName($colName);
                foreach ($aliases as $alias) {
                    if ($normalizedCol === $this->normalizeColumnName($alias)) {
                        $map[$field] = $index;
                        break 2;
                    }
                }
            }

            // Second pass: partial match (if not found)
            // Solo matchea si el alias aparece como palabra completa dentro del nombre
            // de columna (evita que "prototipo" matchee con "tipo")
            if (!isset($map[$field])) {
                foreach ($header as $index => $colName) {
                    // Saltar columnas ya asignadas a otro campo
                    if (in_array($index, $map)) continue;

                    $normalizedCol = $this->normalizeColumnName($colName);
                    foreach ($aliases as $alias) {
                        $normalizedAlias = $this->normalizeColumnName($alias);
                        // Solo unidireccional: alias contenido en columna (no al revés)
                        // Y debe ser palabra completa (word boundary)
                        if (mb_strlen($normalizedAlias) >= 3 &&
                            preg_match('/\b' . preg_quote($normalizedAlias, '/') . '\b/', $normalizedCol)) {
                            $map[$field] = $index;
                            break 2;
                        }
                    }
                }
            }
        }

        return $map;
    }

    /**
     * Map month columns
     */
    protected function mapMonthColumns($header) {
        $map = [];

        foreach ($header as $index => $colName) {
            $normalizedCol = $this->normalizeColumnName($colName);
            foreach ($this->monthNames as $monthName => $monthNum) {
                if ($normalizedCol === $monthName) {
                    $map[$monthName] = $index;
                    break;
                }
            }
        }

        return $map;
    }

    /**
     * Normalize column name for matching
     */
    protected function normalizeColumnName($name) {
        // Convert to string and trim
        $name = trim((string)$name);

        // Remove BOM and non-printable characters
        $name = preg_replace('/[\x00-\x1F\x7F\xEF\xBB\xBF]/', '', $name);

        // Lowercase
        $name = mb_strtolower($name, 'UTF-8');

        // Normalize whitespace
        $name = preg_replace('/\s+/', ' ', $name);

        // Remove accents (extended list)
        $accents = [
            'á' => 'a', 'à' => 'a', 'ä' => 'a', 'â' => 'a', 'ã' => 'a',
            'é' => 'e', 'è' => 'e', 'ë' => 'e', 'ê' => 'e',
            'í' => 'i', 'ì' => 'i', 'ï' => 'i', 'î' => 'i',
            'ó' => 'o', 'ò' => 'o', 'ö' => 'o', 'ô' => 'o', 'õ' => 'o',
            'ú' => 'u', 'ù' => 'u', 'ü' => 'u', 'û' => 'u',
            'ñ' => 'n', 'ç' => 'c'
        ];
        $name = strtr($name, $accents);

        return $name;
    }

    /**
     * Read CSV file
     */
    protected function readCSV($filePath) {
        $result = ['header' => [], 'rows' => []];

        // Leer contenido raw para detectar encoding
        $rawContent = file_get_contents($filePath);
        if ($rawContent === false) {
            $this->errors[] = 'No se puede abrir el archivo';
            return $result;
        }

        // Detectar y convertir encoding a UTF-8
        // Excel en español suele exportar como Windows-1252 o Latin-1
        $encoding = mb_detect_encoding($rawContent, ['UTF-8', 'Windows-1252', 'ISO-8859-1', 'ASCII'], true);
        if ($encoding && $encoding !== 'UTF-8') {
            $rawContent = mb_convert_encoding($rawContent, 'UTF-8', $encoding);
            // Re-escribir archivo temporal convertido
            file_put_contents($filePath, $rawContent);
            $this->warnings[] = "Archivo CSV convertido de {$encoding} a UTF-8";
        }

        $handle = fopen($filePath, 'r');
        if (!$handle) {
            $this->errors[] = 'No se puede abrir el archivo';
            return $result;
        }

        // Detect delimiter
        $firstLine = fgets($handle);
        rewind($handle);

        $delimiter = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ',';

        // Read header
        $header = fgetcsv($handle, 0, $delimiter);
        if (!$header) {
            fclose($handle);
            $this->errors[] = 'No se puede leer el encabezado';
            return $result;
        }

        // Clean header (remove BOM, trim)
        $result['header'] = array_map(function($h) {
            $h = trim($h);
            $h = preg_replace('/^\xEF\xBB\xBF/', '', $h); // Remove BOM
            return $h;
        }, $header);

        // Read data rows (con límite de filas para proteger memoria)
        $maxRows = 5000;
        $rowCount = 0;
        while (($row = fgetcsv($handle, 0, $delimiter)) !== false) {
            if ($rowCount >= $maxRows) {
                $this->warnings[] = "Se procesaron solo las primeras {$maxRows} filas. El archivo tiene más datos.";
                break;
            }
            $result['rows'][] = $row;
            $rowCount++;
        }

        fclose($handle);
        return $result;
    }

    /**
     * Read Excel file
     */
    protected function readExcel($filePath) {
        $result = ['header' => [], 'rows' => []];

        // Usar copia local de SimpleXLSXReader (independiente de beztaxcalendar)
        $readerPath = DOKU_PLUGIN . 'taskmassivas/lib/SimpleXLSXReader.php';
        if (!file_exists($readerPath)) {
            $this->errors[] = 'Lector de Excel no disponible (SimpleXLSXReader.php no encontrado)';
            return $result;
        }

        require_once($readerPath);

        $xlsx = new \dokuwiki\plugin\taskmassivas\lib\SimpleXLSXReader($filePath);

        if ($xlsx->hasErrors()) {
            $this->errors[] = 'Error de Excel: ' . implode(', ', $xlsx->getErrors());
            return $result;
        }

        $rows = $xlsx->rows();
        if (empty($rows)) {
            $this->errors[] = 'No hay datos en el archivo Excel';
            return $result;
        }

        // First row is header
        $result['header'] = array_map('trim', $rows[0]);

        // Rest are data rows (con límite para proteger memoria)
        $maxRows = 5000;
        $totalRows = count($rows);
        $limit = min($totalRows, $maxRows + 1); // +1 por header

        for ($i = 1; $i < $limit; $i++) {
            $result['rows'][] = $rows[$i];
        }

        if ($totalRows > $maxRows + 1) {
            $this->warnings[] = "Se procesaron solo las primeras {$maxRows} filas. El archivo tiene " . ($totalRows - 1) . " filas de datos.";
        }

        // Liberar memoria del array completo
        unset($rows);

        return $result;
    }

    /**
     * Format task content with structured DokuWiki table
     *
     * Format:
     * ^  PLAN MENSUAL DE TRABAJO - TIPO: {TYPE}  ^^
     * ^  Período:|{month} {year}  |
     * ^  Fecha límite:|{date}  |
     * ^  Cliente:|{client_name}  |
     *
     * {description}
     */
    protected function formatTaskContent($data) {
        // Map type to Spanish label
        $typeLabels = [
            'correction' => 'CORRECTIVA',
            'corrective' => 'CORRECTIVA',
            'preventive' => 'PREVENTIVA',
            'program' => 'PROGRAMADA',
            'critical' => 'CRITICA',
            'importante' => 'IMPORTANTE',
            'urgente' => 'URGENTE'
        ];

        $typeLabel = isset($typeLabels[$data['type']]) ? $typeLabels[$data['type']] : strtoupper($data['type']);

        // Build period string
        $period = $data['month_name'] . ' ' . $data['year'];

        // Build the table with proper spacing for alignment
        // Two spaces at start of col1 = right align
        // Two spaces at end of col2 = left align
        $content = "^  PLAN MENSUAL DE TRABAJO - TIPO: " . $typeLabel . "  ^^\n";
        $content .= "^  Período:|" . $period . "  |\n";
        $content .= "^  Fecha límite:|" . $data['plan_date'] . "  |\n";
        $content .= "\nDESCRIPCION: " . $data['description'];

        return $content;
    }

    /**
     * Check if a task already exists with same project + date + content
     * Evita crear duplicados cuando se sube el mismo archivo dos veces
     */
    protected function isDuplicate($task) {
        $db = $this->helper->getDB();
        if (!$db) return false;

        $sql = "SELECT COUNT(*) as cnt FROM task
                WHERE thread_id = ?
                AND plan_date = ?
                AND content LIKE ? ESCAPE '\\'
                AND (state != 'deleted' OR state IS NULL)";

        // Buscar por descripción raw (la parte única del contenido)
        $contentSearch = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $task['content_raw']) . '%';

        $result = $db->query($sql, $task['project_id'], $task['plan_date'], $contentSearch);
        $row = $db->res2row($result);

        return ($row && intval($row['cnt']) > 0);
    }

    /**
     * Build result array
     */
    protected function buildResult($tasks, $preview) {
        return [
            'success' => empty($this->errors) || $this->stats['valid'] > 0,
            'preview' => $preview,
            'tasks' => $tasks,
            'stats' => $this->stats,
            'errors' => $this->errors,
            'warnings' => $this->warnings
        ];
    }

    /**
     * Get errors
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * Get warnings
     */
    public function getWarnings() {
        return $this->warnings;
    }

    /**
     * Get stats
     */
    public function getStats() {
        return $this->stats;
    }
}
