<?php

namespace App\Services\Import;

use App\Models\ImportMapping;
use App\Models\ImportLog;
use App\Models\Transaction;
use App\Models\Category;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ImportService
{
    protected array $parsers = [
        ExcelParser::class,
        CsvParser::class,
        OfxParser::class,
        PdfParser::class,
    ];

    /**
     * Get appropriate parser for file type
     */
    public function getParser(string $extension): FileParserInterface
    {
        foreach ($this->parsers as $parserClass) {
            if ($parserClass::supports($extension)) {
                return new $parserClass();
            }
        }
        
        throw new \InvalidArgumentException("Unsupported file type: $extension");
    }

    /**
     * Get preview of file contents
     */
    public function getPreview(string $filePath, int $rows = 15): array
    {
        $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        $parser = $this->getParser($extension);
        
        return $parser->getPreview($filePath, $rows);
    }

    /**
     * Get headers from file
     */
    public function getHeaders(string $filePath, array $options = []): array
    {
        $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        $parser = $this->getParser($extension);
        
        return $parser->getHeaders($filePath, $options);
    }

    /**
     * Parse file with mapping
     */
    public function parseFile(string $filePath, ImportMapping $mapping): array
    {
        $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        $parser = $this->getParser($extension);
        
        $options = [
            'header_row' => $mapping->header_row,
            'data_start_row' => $mapping->data_start_row,
        ];
        
        return $parser->parse($filePath, $options);
    }

    /**
     * Apply mapping to a row of data
     */
    public function applyMapping(array $row, ImportMapping $mapping): array
    {
        $mapped = [];
        $columnMappings = $mapping->column_mappings;
        
        foreach ($columnMappings as $field => $config) {
            if (empty($config['columns'])) {
                continue;
            }
            
            $values = [];
            foreach ($config['columns'] as $colIndex) {
                if (isset($row[$colIndex]) && $row[$colIndex] !== null && $row[$colIndex] !== '') {
                    $values[] = $row[$colIndex];
                }
            }
            
            $separator = $config['concat_separator'] ?? ' ';
            $value = implode($separator, $values);
            
            // Processar valor baseado no tipo de campo
            $mapped[$field] = $this->processFieldValue($field, $value, $mapping);
        }
        
        return $mapped;
    }

    /**
     * Process field value based on field type
     */
    protected function processFieldValue(string $field, $value, ImportMapping $mapping)
    {
        if ($value === null || $value === '') {
            return null;
        }
        
        $fieldConfig = ImportMapping::MAPPABLE_FIELDS[$field] ?? null;
        
        if (!$fieldConfig) {
            return $value;
        }
        
        switch ($fieldConfig['type']) {
            case 'date':
                return $this->parseDate($value, $mapping->date_format);
            
            case 'decimal':
                return $this->parseDecimal(
                    $value, 
                    $mapping->decimal_separator, 
                    $mapping->thousands_separator
                );
            
            default:
                return trim((string) $value);
        }
    }

    /**
     * Parse date value
     */
    protected function parseDate($value, string $format): ?string
    {
        if ($value instanceof \DateTimeInterface) {
            return $value->format('Y-m-d');
        }
        
        $value = trim((string) $value);
        
        if (empty($value)) {
            return null;
        }
        
        // Tentar vários formatos comuns
        $formats = [
            $format,
            'd/m/Y',
            'm/d/Y',
            'Y-m-d',
            'd-m-Y',
            'd.m.Y',
            'Y/m/d',
        ];
        
        foreach ($formats as $fmt) {
            try {
                $date = Carbon::createFromFormat($fmt, $value);
                if ($date && $date->format($fmt) === $value) {
                    return $date->format('Y-m-d');
                }
            } catch (\Exception $e) {
                continue;
            }
        }
        
        // Tentar parse genérico
        try {
            return Carbon::parse($value)->format('Y-m-d');
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Parse decimal value
     */
    protected function parseDecimal($value, string $decimalSeparator, string $thousandsSeparator): ?float
    {
        if (is_numeric($value)) {
            return floatval($value);
        }
        
        $value = trim((string) $value);
        
        if (empty($value)) {
            return null;
        }
        
        // Remover símbolos de moeda e espaços
        $value = preg_replace('/[€$R\s]/', '', $value);
        
        // Substituir separadores
        if ($thousandsSeparator !== '') {
            $value = str_replace($thousandsSeparator, '', $value);
        }
        
        if ($decimalSeparator !== '.') {
            $value = str_replace($decimalSeparator, '.', $value);
        }
        
        // Verificar se é numérico após processamento
        if (is_numeric($value)) {
            return floatval($value);
        }
        
        return null;
    }

    /**
     * Import transactions from file
     */
    public function importTransactions(
        string $filePath,
        ImportMapping $mapping,
        int $userId,
        ?int $accountId = null,
        ?int $categoryId = null,
        ?int $costCenterId = null
    ): ImportLog {
        $originalFilename = basename($filePath);
        $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        
        // Usar ID do mapeamento apenas se foi persistido (existe no banco)
        $mappingId = $mapping->exists ? $mapping->id : null;
        
        // Criar log de importação
        $importLog = ImportLog::create([
            'user_id' => $userId,
            'import_mapping_id' => $mappingId,
            'original_filename' => $originalFilename,
            'file_type' => $extension,
            'status' => ImportLog::STATUS_PROCESSING,
        ]);
        
        try {
            // Parse arquivo
            $parsed = $this->parseFile($filePath, $mapping);
            $importLog->update(['total_rows' => $parsed['total_rows']]);
            
            $imported = 0;
            $skipped = 0;
            $errors = 0;
            $errorDetails = [];
            
            // Usar conta/categoria/centro de custo do mapping se não especificados
            $accountId = $accountId ?? $mapping->default_account_id;
            $categoryId = $categoryId ?? $mapping->default_category_id;
            $costCenterId = $costCenterId ?? $mapping->default_cost_center_id;
            
            DB::beginTransaction();
            
            foreach ($parsed['data'] as $rowIndex => $row) {
                try {
                    $mapped = $this->applyMapping($row, $mapping);
                    
                    // Validar campos obrigatórios
                    if (empty($mapped['effective_date']) || !isset($mapped['amount'])) {
                        $skipped++;
                        $errorDetails[] = [
                            'row' => $rowIndex + 1,
                            'error' => 'Campos obrigatórios ausentes (data ou valor)',
                            'reason' => 'missing_required',
                        ];
                        continue;
                    }
                    
                    // Determinar tipo (crédito/débito) baseado no valor
                    $amount = $mapped['amount'];
                    $type = 'debit';
                    
                    if (isset($mapped['type'])) {
                        $typeValue = strtolower($mapped['type']);
                        if (in_array($typeValue, ['credit', 'crédito', 'credito', 'c', '+'])) {
                            $type = 'credit';
                        } elseif (in_array($typeValue, ['debit', 'débito', 'debito', 'd', '-'])) {
                            $type = 'debit';
                        }
                    } elseif ($amount > 0) {
                        $type = 'credit';
                    } elseif ($amount < 0) {
                        $type = 'debit';
                        $amount = abs($amount);
                    }
                    
                    // Obter descrição original (para hash e referência)
                    $originalDescription = $mapped['description'] ?? '';
                    
                    // Obter saldo se disponível no extrato (usado APENAS para o hash)
                    $balance = $mapped['balance'] ?? null;
                    
                    // Gerar hash único para evitar duplicidade (data + valor + descrição + saldo se houver)
                    // O saldo é usado APENAS para diferenciar transações idênticas no mesmo dia
                    // NÃO é armazenado na BD para não interferir no cálculo dinâmico de saldo
                    $importHash = Transaction::generateImportHash(
                        $mapped['effective_date'],
                        $amount,
                        $originalDescription,
                        $balance  // Pode ser null se não mapeado
                    );
                    
                    // Verificar se já existe transação com este hash
                    if (Transaction::existsByHash($userId, $importHash)) {
                        $skipped++;
                        $errorDetails[] = [
                            'row' => $rowIndex + 1,
                            'error' => 'Transação já importada anteriormente',
                            'reason' => 'duplicate',
                            'hash' => substr($importHash, 0, 16) . '...',
                        ];
                        continue;
                    }
                    
                    // Criar transação importada
                    // Nota: Transações importadas são sempre 'completed' e sem categoria
                    // A categorização deve ser feita manualmente pelo usuário após a importação
                    Transaction::create([
                        'user_id' => $userId,
                        'account_id' => $accountId,
                        'category_id' => null, // Importações são sempre sem categoria
                        'cost_center_id' => $costCenterId,
                        'amount' => abs($amount),
                        'planned_amount' => abs($amount),
                        'type' => $type,
                        'description' => $originalDescription,
                        'original_description' => $originalDescription,
                        'effective_date' => $mapped['effective_date'],
                        'planned_date' => $mapped['planned_date'] ?? $mapped['effective_date'],
                        'status' => 'completed', // Importações são sempre concluídas
                        'notes' => $mapped['notes'] ?? null,
                        'reference' => $mapped['reference'] ?? null,
                        'import_hash' => $importHash,
                        'import_log_id' => $importLog->id,
                    ]);
                    
                    $imported++;
                    
                } catch (\Exception $e) {
                    $errors++;
                    $errorDetails[] = [
                        'row' => $rowIndex + 1,
                        'error' => $e->getMessage(),
                        'data' => $row,
                    ];
                    
                    Log::warning("Import error at row $rowIndex", [
                        'error' => $e->getMessage(),
                        'row' => $row,
                    ]);
                }
            }
            
            DB::commit();
            
            $importLog->markAsCompleted($imported, $skipped, $errors, $errorDetails);
            
        } catch (\Exception $e) {
            DB::rollBack();
            $importLog->markAsFailed([
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            
            throw $e;
        }
        
        return $importLog->fresh();
    }

    /**
     * Create predefined mapping for known bank formats
     */
    public function createBankPreset(string $bankName, int $userId): ImportMapping
    {
        $presets = [
            'bbva' => [
                'name' => 'BBVA España',
                'bank_name' => 'BBVA',
                'file_type' => 'xlsx',
                'header_row' => 4,
                'data_start_row' => 5,
                'date_format' => 'd/m/Y',
                'decimal_separator' => ',',
                'thousands_separator' => '.',
                'column_mappings' => [
                    'effective_date' => ['columns' => [0], 'concat_separator' => null],
                    'planned_date' => ['columns' => [1], 'concat_separator' => null],
                    'description' => ['columns' => [2, 3], 'concat_separator' => ' - '],
                    'amount' => ['columns' => [4], 'concat_separator' => null],
                    'notes' => ['columns' => [8], 'concat_separator' => null],
                ],
            ],
            'santander' => [
                'name' => 'Santander España',
                'bank_name' => 'Santander',
                'file_type' => 'xls',
                'header_row' => 7,
                'data_start_row' => 8,
                'date_format' => 'd/m/Y',
                'decimal_separator' => ',',
                'thousands_separator' => '.',
                'column_mappings' => [
                    'effective_date' => ['columns' => [0], 'concat_separator' => null],
                    'planned_date' => ['columns' => [1], 'concat_separator' => null],
                    'description' => ['columns' => [2], 'concat_separator' => null],
                    'amount' => ['columns' => [3], 'concat_separator' => null],
                ],
            ],
            'caixa' => [
                'name' => 'CaixaBank',
                'bank_name' => 'CaixaBank',
                'file_type' => 'xlsx',
                'header_row' => 0,
                'data_start_row' => 1,
                'date_format' => 'd/m/Y',
                'decimal_separator' => ',',
                'thousands_separator' => '.',
                'column_mappings' => [
                    'effective_date' => ['columns' => [0], 'concat_separator' => null],
                    'description' => ['columns' => [1], 'concat_separator' => null],
                    'amount' => ['columns' => [2], 'concat_separator' => null],
                ],
            ],
        ];
        
        $key = strtolower($bankName);
        
        if (!isset($presets[$key])) {
            throw new \InvalidArgumentException("Unknown bank preset: $bankName");
        }
        
        $preset = $presets[$key];
        $preset['user_id'] = $userId;
        
        return ImportMapping::create($preset);
    }

    /**
     * Get available bank presets
     */
    public function getAvailablePresets(): array
    {
        return [
            'bbva' => [
                'name' => 'BBVA España',
                'file_types' => ['xlsx'],
                'description' => 'Extrato BBVA em formato Excel',
            ],
            'santander' => [
                'name' => 'Santander España',
                'file_types' => ['xls'],
                'description' => 'Extrato Santander em formato Excel 97-2003',
            ],
            'caixa' => [
                'name' => 'CaixaBank',
                'file_types' => ['xlsx'],
                'description' => 'Extrato CaixaBank em formato Excel',
            ],
        ];
    }

    /**
     * Suggest mapping based on file headers
     */
    public function suggestMapping(array $headers): array
    {
        $suggestions = [];
        
        $patterns = [
            'effective_date' => [
                '/fecha.*valor/i', '/f\.?\s*valor/i', '/date/i', '/data/i', 
                '/fecha.*operaci[oó]n/i', '/data.*efetiva/i', '/value.*date/i'
            ],
            'planned_date' => [
                '/fecha(?!.*valor)/i', '/planned.*date/i', '/data.*planejada/i',
                '/fecha.*contable/i'
            ],
            'description' => [
                '/concepto/i', '/descri[çc][ãa]o/i', '/description/i', '/memo/i',
                '/movimiento/i', '/name/i', '/payee/i'
            ],
            'amount' => [
                '/importe/i', '/valor/i', '/amount/i', '/monto/i', '/trnamt/i',
                '/quantia/i', '/value/i'
            ],
            'balance' => [
                '/saldo/i', '/balance/i', '/disponible/i', '/available/i'
            ],
            'type' => [
                '/tipo/i', '/type/i', '/trntype/i', '/credit.*debit/i'
            ],
            'notes' => [
                '/observa/i', '/notes/i', '/notas/i', '/comment/i'
            ],
            'reference' => [
                '/refer[êe]ncia/i', '/reference/i', '/fitid/i', '/n[úu]mero/i'
            ],
        ];
        
        foreach ($headers as $index => $header) {
            if ($header === null || $header === '') {
                continue;
            }
            
            $headerStr = (string) $header;
            
            foreach ($patterns as $field => $fieldPatterns) {
                foreach ($fieldPatterns as $pattern) {
                    if (preg_match($pattern, $headerStr)) {
                        if (!isset($suggestions[$field])) {
                            $suggestions[$field] = [
                                'columns' => [$index],
                                'concat_separator' => null,
                                'header_name' => $headerStr,
                            ];
                        }
                        break 2;
                    }
                }
            }
        }
        
        return $suggestions;
    }
}
