<?php

namespace App\Services\Import;

use App\Models\Client;
use App\Models\Country;
use App\Models\ImportBatch;
use App\Models\ImportRecord;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

// PRC-707 — Import orkestratörü. Driver'dan normalize satırları alır,
// entity-başına idempotent persist eder (dublicate yaratmaz), dry-run
// önizlemesi yapar, batch + import_records izini tutar.
//
// Idempotency sözleşmesi: her satır bir external_ref taşır. Aynı entity +
// external_ref için "imported" bir import_record varsa VEYA hedef entity'de
// eşleşen kayıt (client: email) zaten varsa → SKIP. Böylece tekrar
// çalıştırma kopya üretmez.
//
// Güvenlik: import edilen 3rd-party parola-hash'i DÜZ-METNE ÇEVRİLMEZ; ham
// hash korunur (`hashed` cast'i baypaslamak için query-builder insert).
// Login akışı legacy hash'i tespit edip re-hash/reset-daveti yapar (residual).
class ImportService
{
    public function __construct(private ImportManager $manager) {}

    /**
     * Kaynağı analiz et (eşleme UI'ı için başlıklar + örnekler).
     *
     * @param  array<string,mixed>  $source
     * @return array{headers: array<string>, sample: array<mixed>, error?: string}
     */
    public function analyze(string $source, string $entity, array $sourceConfig): array
    {
        $driver = $this->manager->get($source);

        return $driver->analyze($entity, $sourceConfig);
    }

    /**
     * İçe-aktarımı çalıştır. dry_run=true → persist yok, yalnız önizleme sayaçları.
     *
     * @param  array<string,mixed>  $source  driver'a geçilen kaynak config (path / db creds)
     * @param  array<string,?string>  $mapping
     * @param  array<string,mixed>  $options
     */
    public function run(
        string $sourceKey,
        string $entity,
        array $source,
        array $mapping,
        array $options,
        bool $dryRun,
        ?int $adminId,
        ?string $filePath = null,
        ?string $originalFilename = null,
    ): ImportBatch {
        $driver = $this->manager->get($sourceKey);

        $batch = ImportBatch::create([
            'source' => $sourceKey,
            'entity' => $entity,
            'status' => $dryRun ? 'previewed' : 'processing',
            'dry_run' => $dryRun,
            'file_path' => $filePath,
            'original_filename' => $originalFilename,
            // Secret İÇERMEZ: mapping + non-credential seçenekler saklanır.
            'mapping' => $mapping,
            'options' => $this->sanitizeOptions($options),
            'created_by_admin_id' => $adminId,
        ]);

        $imported = 0;
        $skipped = 0;
        $failed = 0;
        $total = 0;
        $errors = [];

        // Yalnız clients MVP'de tam persist edilir; diğerleri iskelet.
        $canPersist = $entity === 'clients';

        foreach ($driver->rows($entity, $source, $mapping, $options) as $row) {
            $total++;

            if (! empty($row['error'])) {
                $failed++;
                if (count($errors) < 100) {
                    $errors[] = $row['error'];
                }

                continue;
            }

            $externalRef = (string) ($row['external_ref'] ?? '');
            $data = $row['data'] ?? [];

            if (! $canPersist) {
                // İskelet entity: normalize var ama persist follow-up.
                $skipped++;

                continue;
            }

            $result = $this->importClient($batch, $entity, $externalRef, $data, $options, $dryRun);

            match ($result['status']) {
                ImportRecord::STATUS_IMPORTED => $imported++,
                ImportRecord::STATUS_SKIPPED => $skipped++,
                default => $failed++,
            };
            if ($result['status'] === ImportRecord::STATUS_FAILED && count($errors) < 100) {
                $errors[] = $result['message'];
            }
        }

        $batch->update([
            'status' => $dryRun ? 'previewed' : 'completed',
            'total' => $total,
            'imported' => $imported,
            'skipped' => $skipped,
            'failed' => $failed,
            'report' => ['errors' => $errors, 'persisted_entity' => $canPersist],
            'completed_at' => now(),
        ]);

        return $batch->fresh();
    }

    /**
     * Tek müşteri satırını idempotent aktar.
     *
     * @param  array<string,mixed>  $data
     * @param  array<string,mixed>  $options
     * @return array{status: string, message: ?string}
     */
    private function importClient(ImportBatch $batch, string $entity, string $externalRef, array $data, array $options, bool $dryRun): array
    {
        $email = mb_strtolower(trim((string) ($data['email'] ?? '')));
        $firstName = trim((string) ($data['first_name'] ?? ''));

        if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $this->record($batch, $entity, $externalRef, ImportRecord::STATUS_FAILED, null, null, 'Geçersiz e-posta', $dryRun);
        }
        if ($firstName === '') {
            return $this->record($batch, $entity, $externalRef, ImportRecord::STATUS_FAILED, null, null, 'first_name boş', $dryRun);
        }

        // Idempotency #1: bu batch-serisinde daha önce imported iz var mı?
        $alreadyImported = ImportRecord::where('entity', $entity)
            ->where('external_ref', $externalRef !== '' ? $externalRef : $email)
            ->where('status', ImportRecord::STATUS_IMPORTED)
            ->exists();
        if ($alreadyImported) {
            return $this->record($batch, $entity, $externalRef, ImportRecord::STATUS_SKIPPED, null, null, 'Daha önce aktarılmış', $dryRun);
        }

        // Idempotency #2: PROCP'de aynı e-posta zaten var mı? (brand_scope=0 global)
        $existing = Client::withTrashed()->where('email', $email)->first();
        if ($existing) {
            return $this->record($batch, $entity, $externalRef, ImportRecord::STATUS_SKIPPED, Client::class, $existing->id, 'E-posta zaten mevcut', $dryRun);
        }

        if ($dryRun) {
            // Önizleme: persist yok, iz de yazma (batch sayaçları yeter).
            return ['status' => ImportRecord::STATUS_IMPORTED, 'message' => null];
        }

        $clientId = null;
        try {
            $clientId = DB::transaction(function () use ($data, $email, $firstName, $options) {
                $countryId = $this->resolveCountryId((string) ($data['country_code'] ?? ''));

                // Parola: import edilen hash KORUNUR (bcrypt geçer). Hash yoksa
                // rastgele güçlü değer (login = şifre-sıfırlama daveti).
                $rawHash = trim((string) ($data['password_hash'] ?? ''));
                $passwordValue = $rawHash !== ''
                    ? $rawHash
                    : Hash::make(Str::random(40)); // düz-metin ASLA saklanmaz

                $now = now();
                $attrs = [
                    'uuid' => (string) Str::uuid(),
                    'type' => trim((string) ($data['company_name'] ?? '')) !== '' ? 'company' : 'individual',
                    'first_name' => mb_substr($firstName, 0, 191),
                    'last_name' => mb_substr(trim((string) ($data['last_name'] ?? '')), 0, 191),
                    'company_name' => mb_substr(trim((string) ($data['company_name'] ?? '')), 0, 191) ?: null,
                    'email' => $email,
                    'phone' => mb_substr(trim((string) ($data['phone'] ?? '')), 0, 191) ?: null,
                    // Query-builder insert → `hashed` cast baypaslanır, ham hash korunur.
                    'password' => $passwordValue,
                    'address1' => mb_substr(trim((string) ($data['address1'] ?? '')), 0, 191) ?: null,
                    'address2' => mb_substr(trim((string) ($data['address2'] ?? '')), 0, 191) ?: null,
                    'city_id' => null,
                    'state' => mb_substr(trim((string) ($data['state'] ?? '')), 0, 191) ?: null,
                    'postcode' => mb_substr(trim((string) ($data['postcode'] ?? '')), 0, 20) ?: null,
                    'tax_id' => mb_substr(trim((string) ($data['tax_id'] ?? '')), 0, 50) ?: null,
                    'country_id' => $countryId,
                    'status' => in_array($data['status'] ?? 'active', ['active', 'inactive', 'suspended'], true)
                        ? $data['status'] : 'active',
                    'group_id' => $options['default_group_id'] ?? null,
                    'currency_id' => $options['default_currency_id'] ?? null,
                    'language_id' => $options['default_language_id'] ?? null,
                    'registered_at' => $now,
                    'created_at' => $now,
                    'updated_at' => $now,
                ];

                return DB::table('clients')->insertGetId($attrs);
            });
        } catch (\Throwable $e) {
            return $this->record($batch, $entity, $externalRef, ImportRecord::STATUS_FAILED, null, null, 'Kayıt oluşturulamadı', $dryRun);
        }

        return $this->record($batch, $entity, $externalRef, ImportRecord::STATUS_IMPORTED, Client::class, $clientId, null, $dryRun);
    }

    /** İz kaydı yaz (dry-run'da yazmaz) + tekdüze dönüş. */
    private function record(ImportBatch $batch, string $entity, string $externalRef, string $status, ?string $targetType, ?int $targetId, ?string $message, bool $dryRun): array
    {
        if (! $dryRun) {
            ImportRecord::create([
                'batch_id' => $batch->id,
                'external_ref' => $externalRef !== '' ? mb_substr($externalRef, 0, 191) : '(none)',
                'entity' => $entity,
                'status' => $status,
                'target_type' => $targetType,
                'target_id' => $targetId,
                'message' => $message ? mb_substr($message, 0, 255) : null,
            ]);
        }

        return ['status' => $status, 'message' => $message];
    }

    private function resolveCountryId(string $code): ?int
    {
        $code = strtoupper(trim($code));
        if ($code === '') {
            return null;
        }

        return Country::where('a2_iso', $code)->value('id');
    }

    /** Credential/secret alanlarını batch options'tan temizle (loglanmasın). */
    private function sanitizeOptions(array $options): array
    {
        unset($options['password'], $options['db_password'], $options['tls_ca']);

        return $options;
    }
}
