<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Models\LeadModel;

/**
 * ImportService
 *
 * CSV import for Leads (Customers follow the identical pattern — see
 * importCustomers() below). Every row is validated individually so one
 * malformed row doesn't abort the whole file; duplicates (matched by
 * email or phone, same rule as LeadModel::findPotentialDuplicates())
 * are skipped, not overwritten, by default. Every run is logged to
 * import_batches so the admin UI can show "214 created, 3 skipped, 2
 * failed" without re-reading the file.
 */
final class ImportService
{
    public function __construct(private readonly LeadModel $leads = new LeadModel())
    {
    }

    /**
     * @param string $csvPath Path to an already-uploaded CSV file on disk.
     * @return array{batch_id:string, total:int, created:int, skipped:int, failed:int, errors:array}
     */
    public function importLeads(string $csvPath, int $businessLineId, int $actingUserId): array
    {
        $batchId = self::uuid();
        $handle = fopen($csvPath, 'r');
        if ($handle === false) {
            throw new \RuntimeException('Could not open the uploaded file.');
        }

        $header = fgetcsv($handle);
        if ($header === false) {
            fclose($handle);
            throw new \RuntimeException('The CSV file appears to be empty.');
        }
        $header = array_map(fn ($h) => strtolower(trim((string) $h)), $header);

        $required = ['first_name', 'email'];
        foreach ($required as $col) {
            if (!in_array($col, $header, true)) {
                fclose($handle);
                throw new \RuntimeException("The CSV must include a '{$col}' column.");
            }
        }

        $stats = ['total' => 0, 'created' => 0, 'skipped' => 0, 'failed' => 0, 'errors' => []];
        $rowNumber = 1; // header was row 1

        while (($row = fgetcsv($handle)) !== false) {
            $rowNumber++;
            if (count(array_filter($row)) === 0) {
                continue; // skip fully blank lines
            }
            $stats['total']++;

            $row = array_slice(array_pad($row, count($header), null), 0, count($header));
            $data = array_combine($header, $row);

            try {
                $result = $this->importOneLead($data, $businessLineId, $batchId, $actingUserId);

                if ($result === 'created') {
                    $stats['created']++;
                } else {
                    $stats['skipped']++;
                    $stats['errors'][] = ['row' => $rowNumber, 'message' => 'Skipped — matches an existing lead (email or phone).'];
                }
            } catch (\Throwable $e) {
                $stats['failed']++;
                $stats['errors'][] = ['row' => $rowNumber, 'message' => $e->getMessage()];
            }
        }

        fclose($handle);
        $this->recordBatch($batchId, 'leads', basename($csvPath), $stats, $actingUserId);

        return ['batch_id' => $batchId, ...$stats];
    }

    private function importOneLead(array $data, int $businessLineId, string $batchId, int $actingUserId): string
    {
        $firstName = trim((string) ($data['first_name'] ?? ''));
        $email = trim((string) ($data['email'] ?? '')) ?: null;
        $phone = trim((string) ($data['phone'] ?? '')) ?: null;

        if ($firstName === '') {
            throw new \RuntimeException('Missing first name.');
        }
        if ($email !== null && filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
            throw new \RuntimeException("Invalid email address: {$email}");
        }

        if ($this->leads->findPotentialDuplicates($email, $phone) !== []) {
            return 'skipped';
        }

        $defaultStage = Database::connection()->query(
            "SELECT id FROM lead_stages WHERE slug = 'new' LIMIT 1"
        )->fetchColumn();

        $this->leads->insert([
            'business_line_id' => $businessLineId,
            'lead_stage_id' => (int) $defaultStage,
            'first_name' => $firstName,
            'last_name' => trim((string) ($data['last_name'] ?? '')) ?: null,
            'email' => $email,
            'phone' => $phone,
            'estimated_value' => is_numeric($data['estimated_value'] ?? null) ? (float) $data['estimated_value'] : 0,
            'currency' => 'CAD',
            'notes' => trim((string) ($data['notes'] ?? '')) ?: null,
            'import_batch_id' => $batchId,
        ], $actingUserId);

        return 'created';
    }

    private function recordBatch(string $batchId, string $entityType, string $filename, array $stats, int $userId): void
    {
        Database::connection()->prepare(
            'INSERT INTO import_batches (id, entity_type, original_filename, total_rows, created_count, skipped_count, failed_count, error_log, imported_by, created_at)
             VALUES (:id, :type, :filename, :total, :created, :skipped, :failed, :errors, :user, NOW())'
        )->execute([
            'id' => $batchId,
            'type' => $entityType,
            'filename' => $filename,
            'total' => $stats['total'],
            'created' => $stats['created'],
            'skipped' => $stats['skipped'],
            'failed' => $stats['failed'],
            'errors' => json_encode($stats['errors']),
            'user' => $userId,
        ]);
    }

    private static function uuid(): string
    {
        $data = random_bytes(16);
        $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
        $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);

        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
}
