<?php

namespace App\Services\Import;

use App\Models\ClientCase;
use App\Models\Contract;
use App\Models\Email;
use App\Models\Import;
use App\Models\Person\Person;
use App\Models\Person\PersonAddress;
use App\Models\Person\PersonPhone;
use Illuminate\Support\Facades\Log;

/**
 * EntityResolutionService - Resolves existing entities to prevent duplication.
 *
 * This service checks for existing entities before creating new ones,
 * following the V1 deduplication hierarchy:
 * 1. Contract reference → ClientCase → Person
 * 2. ClientCase client_ref → Person
 * 3. Contact values (email/phone/address) → Person
 * 4. Person identifiers (tax_number/ssn) → Person
 */
class EntityResolutionService
{
    /**
     * Resolve Person ID from import context (existing entities).
     * Returns Person ID if found, null otherwise.
     *
     * @param Import $import
     * @param array $mapped Mapped data from CSV row
     * @param array $context Processing context with previously processed entities
     * @return int|null Person ID if found, null if should create new
     */
    public function resolvePersonFromContext(Import $import, array $mapped, array $context): ?int
    {
        // 1. Check if Contract already processed in this row
        if ($contract = $context['contract']['entity'] ?? null) {
            $personId = $this->getPersonFromContract($contract);
            if ($personId) {
                Log::info('EntityResolutionService: Found Person from processed Contract', [
                    'person_id' => $personId,
                    'contract_id' => $contract->id,
                ]);
                return $personId;
            }
        }

        // 2. Check if ClientCase already processed in this row
        if ($clientCase = $context['client_case']['entity'] ?? null) {
            if ($clientCase->person_id) {
                Log::info('EntityResolutionService: Found Person from processed ClientCase', [
                    'person_id' => $clientCase->person_id,
                    'client_case_id' => $clientCase->id,
                ]);
                return $clientCase->person_id;
            }
        }

        // 3. Check for existing Contract by reference (before it's processed)
        if ($contractRef = $mapped['contract']['reference'] ?? null) {
            $personId = $this->getPersonFromContractReference($import->client_id, $contractRef);
            if ($personId) {
                Log::info('EntityResolutionService: Found Person from existing Contract reference', [
                    'person_id' => $personId,
                    'contract_reference' => $contractRef,
                ]);
                return $personId;
            }
        }

        // 4. Check for existing ClientCase by client_ref (before it's processed)
        if ($clientRef = $mapped['client_case']['client_ref'] ?? null) {
            $personId = $this->getPersonFromClientRef($import->client_id, $clientRef);
            if ($personId) {
                Log::info('EntityResolutionService: Found Person from existing ClientCase client_ref', [
                    'person_id' => $personId,
                    'client_ref' => $clientRef,
                ]);
                return $personId;
            }
        }

        // 5. Check for existing Person by contact values (email/phone/address)
        $personId = $this->resolvePersonByContacts($mapped);
        if ($personId) {
            Log::info('EntityResolutionService: Found Person from contact values', [
                'person_id' => $personId,
            ]);
            return $personId;
        }

        // No existing Person found
        return null;
    }

    /**
     * Check if ClientCase exists for this client_ref.
     *
     * @param int|null $clientId
     * @param string $clientRef
     * @return bool
     */
    public function clientCaseExists(?int $clientId, string $clientRef): bool
    {
        if (!$clientId || !$clientRef) {
            return false;
        }

        return ClientCase::where('client_id', $clientId)
            ->where('client_ref', $clientRef)
            ->exists();
    }

    /**
     * Check if Contract exists for this reference.
     *
     * @param int|null $clientId
     * @param string $reference
     * @return bool
     */
    public function contractExists(?int $clientId, string $reference): bool
    {
        if (!$clientId || !$reference) {
            return false;
        }

        return Contract::query()
            ->join('client_cases', 'contracts.client_case_id', '=', 'client_cases.id')
            ->where('client_cases.client_id', $clientId)
            ->where('contracts.reference', $reference)
            ->exists();
    }

    /**
     * Get existing ClientCase by client_ref.
     *
     * @param int|null $clientId
     * @param string $clientRef
     * @return ClientCase|null
     */
    public function getExistingClientCase(?int $clientId, string $clientRef): ?ClientCase
    {
        if (!$clientId || !$clientRef) {
            return null;
        }

        return ClientCase::where('client_id', $clientId)
            ->where('client_ref', $clientRef)
            ->first();
    }

    /**
     * Get existing Contract by reference for this client.
     *
     * @param int|null $clientId
     * @param string $reference
     * @return Contract|null
     */
    public function getExistingContract(?int $clientId, string $reference): ?Contract
    {
        if (!$clientId || !$reference) {
            return null;
        }

        return Contract::query()
            ->join('client_cases', 'contracts.client_case_id', '=', 'client_cases.id')
            ->where('client_cases.client_id', $clientId)
            ->where('contracts.reference', $reference)
            ->select('contracts.*')
            ->first();
    }

    /**
     * Get Person ID from a Contract entity.
     *
     * @param Contract $contract
     * @return int|null
     */
    protected function getPersonFromContract(Contract $contract): ?int
    {
        if ($contract->client_case_id) {
            return ClientCase::where('id', $contract->client_case_id)
                ->value('person_id');
        }

        return null;
    }

    /**
     * Get Person ID from existing Contract by reference.
     *
     * @param int|null $clientId
     * @param string $reference
     * @return int|null
     */
    protected function getPersonFromContractReference(?int $clientId, string $reference): ?int
    {
        if (!$clientId) {
            return null;
        }

        $clientCaseId = Contract::query()
            ->join('client_cases', 'contracts.client_case_id', '=', 'client_cases.id')
            ->where('client_cases.client_id', $clientId)
            ->where('contracts.reference', $reference)
            ->value('contracts.client_case_id');

        if ($clientCaseId) {
            return ClientCase::where('id', $clientCaseId)
                ->value('person_id');
        }

        return null;
    }

    /**
     * Get Person ID from existing ClientCase by client_ref.
     *
     * @param int|null $clientId
     * @param string $clientRef
     * @return int|null
     */
    protected function getPersonFromClientRef(?int $clientId, string $clientRef): ?int
    {
        if (!$clientId) {
            return null;
        }

        return ClientCase::where('client_id', $clientId)
            ->where('client_ref', $clientRef)
            ->value('person_id');
    }

    /**
     * Resolve Person by contact values (email, phone, address).
     * Checks existing contact records and returns associated Person ID.
     *
     * @param array $mapped
     * @return int|null
     */
    protected function resolvePersonByContacts(array $mapped): ?int
    {
        // Check email (support both single and array formats)
        $email = $this->extractContactValue($mapped, 'email', 'value', 'emails');
        if ($email) {
            $personId = Email::where('value', trim($email))->value('person_id');
            if ($personId) {
                return $personId;
            }
        }

        // Check phone (support both single and array formats)
        $phone = $this->extractContactValue($mapped, 'phone', 'nu', 'person_phones');
        if ($phone) {
            $personId = PersonPhone::where('nu', trim($phone))->value('person_id');
            if ($personId) {
                return $personId;
            }
        }

        // Check address (support both single and array formats)
        $address = $this->extractContactValue($mapped, 'address', 'address', 'person_addresses');
        if ($address) {
            $personId = PersonAddress::where('address', trim($address))->value('person_id');
            if ($personId) {
                return $personId;
            }
        }

        return null;
    }

    /**
     * Extract contact value from mapped data, supporting multiple formats.
     *
     * @param array $mapped
     * @param string $singularKey e.g., 'email', 'phone', 'address'
     * @param string $field Field name within the contact data
     * @param string $pluralKey e.g., 'emails', 'person_phones', 'person_addresses'
     * @return string|null
     */
    protected function extractContactValue(array $mapped, string $singularKey, string $field, string $pluralKey): ?string
    {
        // Try singular key first (e.g., 'email')
        if (isset($mapped[$singularKey][$field])) {
            return $mapped[$singularKey][$field];
        }

        // Try plural key (e.g., 'emails')
        if (isset($mapped[$pluralKey])) {
            // If it's an array of contacts
            if (is_array($mapped[$pluralKey])) {
                // Try first element if it's an indexed array
                if (isset($mapped[$pluralKey][0][$field])) {
                    return $mapped[$pluralKey][0][$field];
                }
                // Try direct field access if it's a single hash
                if (isset($mapped[$pluralKey][$field])) {
                    return $mapped[$pluralKey][$field];
                }
            }
        }

        return null;
    }

    /**
     * Check if this row should skip Person creation based on existing entities.
     * Used by PersonHandler to determine if Person already exists via chain.
     *
     * @param Import $import
     * @param array $mapped
     * @param array $context
     * @return bool True if Person should be skipped (already exists)
     */
    public function shouldSkipPersonCreation(Import $import, array $mapped, array $context): bool
    {
        // If we can resolve existing Person, we should skip creation
        $personId = $this->resolvePersonFromContext($import, $mapped, $context);

        return $personId !== null;
    }

    /**
     * Get or create ClientCase for Contract creation.
     * Reuses existing ClientCase if found by client_ref.
     *
     * @param Import $import
     * @param array $mapped
     * @param array $context
     * @return int|null ClientCase ID
     */
    public function resolveOrCreateClientCaseForContract(Import $import, array $mapped, array $context): ?int
    {
        $clientId = $import->client_id;

        if (!$clientId) {
            return null;
        }

        // If ClientCase already processed in this row, use it
        if ($clientCaseId = $context['client_case']['entity']?->id ?? null) {
            return $clientCaseId;
        }

        // Try to find by client_ref
        $clientRef = $mapped['client_case']['client_ref'] ?? $mapped['client_ref'] ?? null;

        if ($clientRef) {
            $existing = $this->getExistingClientCase($clientId, $clientRef);

            if ($existing) {
                Log::info('EntityResolutionService: Reusing existing ClientCase for Contract', [
                    'client_case_id' => $existing->id,
                    'client_ref' => $clientRef,
                ]);

                return $existing->id;
            }
        }

        // Need to create new ClientCase
        // Get Person from context (should be processed before Contract now)
        $personId = $context['person']['entity']?->id ?? null;

        if (!$personId) {
            // Person wasn't in import or wasn't found, try to resolve
            $personId = $this->resolvePersonFromContext($import, $mapped, $context);

            if (!$personId) {
                // Create minimal Person as last resort
                $defaultGroupId = (int) (\App\Models\Person\PersonGroup::min('id') ?? 1);
                $personId = Person::create([
                    'type_id' => 1,
                    'group_id' => $defaultGroupId,
                ])->id;
                Log::info('EntityResolutionService: Created minimal Person for new ClientCase', [
                    'person_id' => $personId,
                    'group_id' => $defaultGroupId,
                ]);
            }
        }

        $clientCase = ClientCase::create([
            'client_id' => $clientId,
            'person_id' => $personId,
            'client_ref' => $clientRef,
        ]);

        Log::info('EntityResolutionService: Created new ClientCase', [
            'client_case_id' => $clientCase->id,
            'person_id' => $personId,
            'client_ref' => $clientRef,
        ]);

        return $clientCase->id;
    }
}
