<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Lead;
use App\Models\LeadProduct;
use App\Models\LeadRepresentative;
use App\Models\LeadTechnicalProfile;
use App\Models\LeadStatusHistory;
use App\Models\LossReason;
use App\Models\Notification;

class LeadService
{
    private Lead $leadModel;
    private LeadTechnicalProfile $profileModel;
    private LeadStatusHistory $historyModel;
    private LossReason $lossReasonModel;
    private LeadProduct $leadProductModel;
    private LeadRepresentative $repModel;
    private OpenAiService $openAiService;
    private GoogleSheetsService $sheetsService;
    private ConfigurationService $configService;

    public function __construct()
    {
        $this->leadModel = new Lead();
        $this->profileModel = new LeadTechnicalProfile();
        $this->historyModel = new LeadStatusHistory();
        $this->lossReasonModel = new LossReason();
        $this->leadProductModel = new LeadProduct();
        $this->repModel = new LeadRepresentative();
        $this->openAiService = new OpenAiService();
        $this->sheetsService = new GoogleSheetsService();
        $this->configService = new ConfigurationService();
    }

    public function createManual(array $data, int $userId, int $eventId, string $source = 'manual'): array
    {
        $this->validateLeadData($data, $source);

        $duplicates = $this->checkDuplicates(
            $data['email'] ?? null,
            $data['phone'] ?? null
        );

        $finalEventId = isset($data['event_id']) ? (int) $data['event_id'] : $eventId;
        $leadData = [
            'event_id' => $finalEventId > 0 ? $finalEventId : null,
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'],
            'email' => $data['email'] ?? null,
            'phone' => $data['phone'] ?? null,
            'company' => $data['company'] ?? null,
            'position' => $data['position'] ?? null,
            'city' => $data['city'] ?? null,
            'region' => $data['region'] ?? null,
            'ruc' => $data['ruc'] ?? null,
            'business_name' => $data['business_name'] ?? null,
            'address' => $data['address'] ?? null,
            'audio_file_path' => $data['audio_file_path'] ?? null,
            'photo_file_path' => $data['photo_file_path'] ?? null,
            'client_type' => isset($data['client_type']) ? json_encode($data['client_type']) : null,
            'industry' => isset($data['industry']) ? json_encode($data['industry']) : null,
            'classification' => $data['classification'] ?? null,
            'observations' => $data['observations'] ?? null,
            'source' => $source,
            'created_by' => $userId,
        ];

        if (!empty($duplicates)) {
            $leadData['is_duplicate'] = 1;
            $leadData['duplicate_of'] = (int) $duplicates[0]['id'];
        }

        \App\Models\Database::beginTransaction();
        try {
            $leadId = $this->leadModel->createLead($leadData);

            if (!empty($data['technical_profile'])) {
                $this->profileModel->createOrUpdate($leadId, $data['technical_profile']);
            }

            if (!empty($data['representatives'])) {
                $this->repModel->saveForLead($leadId, $data['representatives']);
            }

            \App\Models\Database::commit();
        } catch (\Exception $e) {
            \App\Models\Database::rollback();
            throw $e;
        }

        $lead = $this->getLead($leadId);

        $this->syncToGoogleSheets($lead);

        return $lead;
    }

    public function parseFromScanOpenAi(array $fileData): array
    {
        $filePath = $this->validateAndStoreUpload($fileData, 'image', ['image/jpeg', 'image/png', 'image/webp']);

        $parsed = $this->openAiService->extractBusinessCardData($filePath);

        if (empty($parsed['first_name']) && empty($parsed['last_name'])) {
            throw new \RuntimeException('No se pudo extraer información de la imagen con OpenAI. Intente con captura manual.', 422);
        }

        return [
            'first_name' => $parsed['first_name'] ?: '',
            'last_name' => $parsed['last_name'] ?: '',
            'email' => $parsed['email'] ?: '',
            'phone' => $parsed['phone'] ?: '',
            'company' => $parsed['company'] ?: '',
            'position' => $parsed['position'] ?: '',
            'city' => $parsed['city'] ?: '',
            'ruc' => $parsed['ruc'] ?: '',
            'photo_file_path' => $this->getRelativePath($filePath),
        ];
    }

    public function parseFromVoiceOpenAi(array $fileData): array
    {
        $filePath = $this->validateAndStoreUpload($fileData, 'audio', ['audio/webm', 'video/webm', 'audio/mp4', 'audio/mpeg', 'audio/wav', 'audio/ogg']);

        $parsed = $this->openAiService->transcribeAudio($filePath);

        if (empty($parsed['first_name']) && empty($parsed['last_name']) && empty($parsed['raw_transcript'])) {
            throw new \RuntimeException('No se pudo procesar el audio con OpenAI. Intente de nuevo o capture manualmente.', 422);
        }

        return [
            'first_name' => $parsed['first_name'] ?: '',
            'last_name' => $parsed['last_name'] ?: '',
            'email' => $parsed['email'] ?: '',
            'phone' => $parsed['phone'] ?: '',
            'company' => $parsed['company'] ?: '',
            'position' => $parsed['position'] ?: '',
            'city' => $parsed['city'] ?: '',
            'region' => $parsed['region'] ?: '',
            'raw_transcript' => $parsed['raw_transcript'] ?: '',
            'audio_file_path' => $this->getRelativePath($filePath),
        ];
    }

    public function checkDuplicates(?string $email, ?string $phone): array
    {
        if (empty($email) && empty($phone)) {
            return [];
        }

        return $this->leadModel->findByEmailAndPhone($email, $phone);
    }

    public function getLead(int $id): array
    {
        $lead = $this->leadModel->findById($id);
        if (!$lead) {
            throw new \RuntimeException('Lead no encontrado', 404);
        }

        $this->decodeLeadJsonFields($lead);

        if (!empty($lead['created_by'])) {
            $creator = $this->leadModel->queryOne("SELECT name FROM users WHERE id = :id", ['id' => $lead['created_by']]);
            $lead['created_by_name'] = $creator['name'] ?? null;
        }

        if (!empty($lead['assigned_to'])) {
            $assignee = $this->leadModel->queryOne("SELECT name FROM users WHERE id = :id", ['id' => $lead['assigned_to']]);
            $lead['assigned_to_name'] = $assignee['name'] ?? null;
        }

        $profile = $this->profileModel->findByLeadId($id);
        $lead['technical_profile'] = $profile;

        $lead['representatives'] = $this->repModel->findByLeadId($id);

        return $lead;
    }

    private function syncToGoogleSheets(array $lead): void
    {
        try {
            if (!$this->sheetsService->isConfigured()) {
                return;
            }

            $config = $this->configService->findByKey('google_sheets_spreadsheet_id');
            $spreadsheetId = $config['value'] ?? null;

            if (empty($spreadsheetId)) {
                return;
            }

            $this->sheetsService->appendLead($lead, $spreadsheetId);
        } catch (\Exception $e) {
            error_log('Sync to Google Sheets failed: ' . $e->getMessage());
        }
    }

    public function getLeads(int $userId, string $role, array $filters = [], int $page = 1, int $perPage = 15): array
    {
        if ($role !== 'super_admin') {
            $filters['created_by'] = $userId;
        } else {
            unset($filters['event_id']);
        }

        $result = $this->leadModel->findByEvent(
            $filters['event_id'] ?? 0,
            $filters,
            $page,
            $perPage
        );

        foreach ($result['items'] as &$lead) {
            $this->decodeLeadJsonFields($lead);
        }

        return $result;
    }

    private function decodeLeadJsonFields(array &$lead): void
    {
        if (!empty($lead['client_type']) && is_string($lead['client_type'])) {
            $lead['client_type'] = json_decode($lead['client_type'], true);
        }
        if (!empty($lead['industry']) && is_string($lead['industry'])) {
            $lead['industry'] = json_decode($lead['industry'], true);
        }
    }

    public function getLeadDetail(int $id): array
    {
        $lead = $this->getLead($id);

        $history = $this->historyModel->getByLeadId($id);
        $lead['status_history'] = $history;
        $lead['timeline'] = $this->buildTimeline($history);
        $lead['products'] = $this->leadProductModel->getByLeadId($id);

        return $lead;
    }

    public function assignToSeller(int $leadId, int $sellerId, int $adminId): array
    {
        $lead = $this->leadModel->findById($leadId);
        if (!$lead) {
            throw new \RuntimeException('Lead no encontrado', 404);
        }

        if ((int) $lead['created_by'] !== $adminId) {
            throw new \RuntimeException('Solo puedes asignar leads que tú creaste', 403);
        }

        $db = \App\Models\Database::connect();
        $stmt = $db->prepare("UPDATE leads SET assigned_to = :assigned_to WHERE id = :id");
        $stmt->execute(['assigned_to' => $sellerId, 'id' => $leadId]);

        return $this->getLead($leadId);
    }

    public function updateLead(int $id, array $data, int $updatedBy): array
    {
        $lead = $this->leadModel->findById($id);
        if (!$lead) {
            throw new \RuntimeException('Lead no encontrado', 404);
        }

        $updateData = [];
        foreach (['first_name', 'last_name', 'email', 'phone', 'company', 'position', 'city', 'region', 'ruc', 'business_name', 'address', 'audio_file_path', 'photo_file_path', 'classification', 'observations', 'lat', 'lng'] as $field) {
            if (array_key_exists($field, $data)) {
                $updateData[$field] = $data[$field];
            }
        }

        if (array_key_exists('client_type', $data)) {
            $updateData['client_type'] = json_encode($data['client_type']);
        }
        if (array_key_exists('industry', $data)) {
            $updateData['industry'] = json_encode($data['industry']);
        }

        if (!empty($updateData)) {
            $updateData['updated_by'] = $updatedBy;
            $this->leadModel->update($id, $updateData);
        }

        if (array_key_exists('representatives', $data)) {
            $this->repModel->saveForLead($id, $data['representatives'] ?? []);
        }

        if (!empty($data['technical_profile'])) {
            $this->profileModel->createOrUpdate($id, $data['technical_profile']);
        }

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

    public function changeStatus(int $id, string $newStatus, ?int $lossReasonId, ?string $notes, int $changedBy): array
    {
        $lead = $this->leadModel->findById($id);
        if (!$lead) {
            throw new \RuntimeException('Lead no encontrado', 404);
        }

        $validStatuses = ['nuevo', 'contactado', 'calificado', 'propuesta', 'negociacion', 'ganado', 'perdido'];
        if (!in_array($newStatus, $validStatuses)) {
            throw new \RuntimeException('Estado no válido: ' . $newStatus, 422);
        }

        if ($newStatus === 'perdido' && empty($lossReasonId)) {
            throw new \RuntimeException('Debe seleccionar un motivo de pérdida', 422);
        }

        if ($newStatus === $lead['status']) {
            throw new \RuntimeException('El lead ya se encuentra en ese estado', 422);
        }

        $previousStatus = $lead['status'];

        $this->leadModel->update($id, [
            'status' => $newStatus,
            'loss_reason_id' => $newStatus === 'perdido' ? $lossReasonId : null,
            'updated_by' => $changedBy,
        ]);

        $this->historyModel->recordChange(
            $id,
            $previousStatus,
            $newStatus,
            $newStatus === 'perdido' ? $lossReasonId : null,
            $notes,
            $changedBy
        );

        $statusLabels = ['nuevo'=>'Nuevo','contactado'=>'Contactado','calificado'=>'Calificado','propuesta'=>'Propuesta','negociacion'=>'Negociación','ganado'=>'Ganado','perdido'=>'Perdido'];
        if ((int) $lead['created_by'] !== $changedBy) {
            $notif = new Notification();
            $notif->createNotification([
                'user_id' => (int) $lead['created_by'],
                'type' => 'actividad',
                'title' => 'Estado actualizado: ' . ($statusLabels[$newStatus] ?? $newStatus),
                'message' => "Lead {$lead['first_name']} {$lead['last_name']} cambió a " . ($statusLabels[$newStatus] ?? $newStatus),
                'reference_type' => 'lead',
                'reference_id' => $id,
            ]);
        }

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

    private function buildTimeline(array $history): array
    {
        return array_map(function ($entry) {
            return [
                'type' => 'status_change',
                'date' => $entry['created_at'],
                'data' => [
                    'from' => $entry['previous_status'],
                    'to' => $entry['new_status'],
                    'notes' => $entry['notes'],
                    'changed_by' => $entry['changed_by_name'],
                ],
            ];
        }, $history);
    }

    public function getLossReasons(): array
    {
        return $this->lossReasonModel->findAllActive();
    }

    private function validateLeadData(array $data, string $source): void
    {
        $errors = [];

        if (empty($data['first_name'] ?? null)) {
            $errors[] = 'El nombre es obligatorio';
        }
        if (empty($data['last_name'] ?? null)) {
            $errors[] = 'El apellido es obligatorio';
        }

        if (!empty($errors)) {
            throw new \RuntimeException(implode('. ', $errors), 422);
        }
    }

    private function validateAndStoreUpload(array $fileData, string $type, array $allowedMimes): string
    {
        if (empty($fileData) || !isset($fileData['tmp_name'])) {
            throw new \RuntimeException('Archivo no proporcionado o error en la subida. Verifica que post_max_size y upload_max_filesize en PHP sean suficientes.', 422);
        }

        $uploadErrors = [
            UPLOAD_ERR_INI_SIZE => 'El archivo excede el límite de upload_max_filesize en PHP',
            UPLOAD_ERR_FORM_SIZE => 'El archivo excede el límite MAX_FILE_SIZE del formulario',
            UPLOAD_ERR_PARTIAL => 'El archivo fue subido parcialmente',
            UPLOAD_ERR_NO_FILE => 'No se recibió ningún archivo',
            UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal de PHP',
            UPLOAD_ERR_CANT_WRITE => 'Error al escribir el archivo en el disco',
            UPLOAD_ERR_EXTENSION => 'Una extensión de PHP bloqueó la subida',
        ];

        if ($fileData['error'] !== UPLOAD_ERR_OK) {
            $msg = $uploadErrors[$fileData['error']] ?? 'Error desconocido en la subida';
            throw new \RuntimeException($msg, 422);
        }

        $mimeType = (new \finfo(FILEINFO_MIME_TYPE))->file($fileData['tmp_name']) ?: '';
        $validMime = false;
        foreach ($allowedMimes as $allowed) {
            if (str_starts_with($mimeType, $allowed) || $mimeType === $allowed) {
                $validMime = true;
                break;
            }
        }

        if (!$validMime) {
            throw new \RuntimeException("Tipo de archivo no permitido para {$type}. Tipos aceptados: " . implode(', ', $allowedMimes), 422);
        }

        $maxSize = $type === 'image' ? 10 * 1024 * 1024 : 30 * 1024 * 1024;
        if ($fileData['size'] > $maxSize) {
            $maxMB = $maxSize / (1024 * 1024);
            throw new \RuntimeException("El archivo excede el tamaño máximo de {$maxMB}MB", 422);
        }

        $extension = $type === 'image' ? 'jpg' : 'webm';
        $filename = uniqid("lead_{$type}_") . '.' . $extension;
        $subDir = STORAGE_PATH . '/uploads/' . ($type === 'image' ? '' : 'audio/');
        $destPath = $subDir . $filename;

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

        if (!move_uploaded_file($fileData['tmp_name'], $destPath)) {
            throw new \RuntimeException('Error al guardar el archivo', 500);
        }

        return $destPath;
    }

    private function getRelativePath(string $absolutePath): string
    {
        $prefix = STORAGE_PATH . '/';
        if (str_starts_with($absolutePath, $prefix)) {
            return substr($absolutePath, strlen($prefix));
        }
        return $absolutePath;
    }

    public function exportLeads(array $filters = [], int $userId = 0, string $role = ''): string
    {
        $leadModel = new \App\Models\Lead();
        $eventId = $filters['event_id'] ?? 0;

        if ($role !== 'super_admin') {
            $filters['created_by'] = $userId;
        } else {
            $eventId = 0;
        }

        $result = $leadModel->findByEvent($eventId, $filters, 1, 1000);
        $leads = $result['items'];

        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();

        $headers = ['Nombre', 'Apellido', 'Email', 'Teléfono', 'Empresa', 'Cargo', 'Ciudad', 'Región', 'Tipo Cliente', 'Rubro', 'Clasificación', 'Observaciones', 'Estado', 'Origen', 'Fecha'];
        $col = 'A';
        foreach ($headers as $header) {
            $sheet->setCellValue($col . '1', $header);
            $sheet->getStyle($col . '1')->getFont()->setBold(true);
            $col++;
        }

        $row = 2;
        foreach ($leads as $lead) {
            $sheet->setCellValue('A' . $row, $lead['first_name'] ?? '');
            $sheet->setCellValue('B' . $row, $lead['last_name'] ?? '');
            $sheet->setCellValue('C' . $row, $lead['email'] ?? '');
            $sheet->setCellValue('D' . $row, $lead['phone'] ?? '');
            $sheet->setCellValue('E' . $row, $lead['company'] ?? '');
            $sheet->setCellValue('F' . $row, $lead['position'] ?? '');
            $clientType = !empty($lead['client_type']) ? (is_array($lead['client_type']) ? implode(', ', $lead['client_type']) : $lead['client_type']) : '';
            $industry = !empty($lead['industry']) ? (is_array($lead['industry']) ? implode(', ', $lead['industry']) : $lead['industry']) : '';
            $sheet->setCellValue('G' . $row, $lead['city'] ?? '');
            $sheet->setCellValue('H' . $row, $lead['region'] ?? '');
            $sheet->setCellValue('I' . $row, $clientType);
            $sheet->setCellValue('J' . $row, $industry);
            $sheet->setCellValue('K' . $row, $lead['classification'] ?? '');
            $sheet->setCellValue('L' . $row, $lead['observations'] ?? '');
            $sheet->setCellValue('M' . $row, $lead['status'] ?? '');
            $sheet->setCellValue('N' . $row, $lead['source'] ?? '');
            $sheet->setCellValue('O' . $row, $lead['created_at'] ?? '');
            $row++;
        }

        foreach (range('A', 'O') as $col) {
            $sheet->getColumnDimension($col)->setAutoSize(true);
        }

        $filename = 'leads_export_' . date('Ymd_His') . '.xlsx';
        $filePath = STORAGE_PATH . '/exports/' . $filename;

        $dir = dirname($filePath);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }

        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
        $writer->save($filePath);

        return $filePath;
    }

    public function deleteLead(int $id, int $userId, string $role = ''): void
    {
        $lead = $this->leadModel->findById($id);
        if (!$lead) {
            throw new \RuntimeException('Lead no encontrado', 404);
        }

        if ($role !== 'super_admin' && (int) $lead['created_by'] !== $userId) {
            throw new \RuntimeException('No tienes permiso para eliminar este lead', 403);
        }

        $this->leadModel->delete($id);
    }

    public function getReferences(int $userId, string $role, int $eventId): array
    {
        return [
            'companies' => $this->leadModel->getCompanies($eventId, $userId, $role),
            'cities' => $this->leadModel->getCities($eventId, $userId, $role),
        ];
    }
}
