<?php

declare(strict_types=1);

namespace ImovelZapAi\Importing\Application\Actions;

use Illuminate\Support\Facades\DB;
use ImovelZapAi\Importing\Application\Data\ParseIssue;
use ImovelZapAi\Importing\Application\Ports\VrSyncParserInterface;
use ImovelZapAi\Importing\Application\Services\ImportFileStorage;
use ImovelZapAi\Importing\Application\Services\ImportListingValidator;
use ImovelZapAi\Importing\Application\Services\ImportLogWriter;
use ImovelZapAi\Importing\Application\Services\ImportMappingResolver;
use ImovelZapAi\Importing\Domain\Enums\ImportIssueSeverity;
use ImovelZapAi\Importing\Domain\Enums\ImportJobStatus;
use ImovelZapAi\Importing\Domain\Enums\ImportLogEvent;
use ImovelZapAi\Importing\Domain\Exceptions\ImportException;
use ImovelZapAi\Importing\Infrastructure\Persistence\Eloquent\ImportErrorModel;
use ImovelZapAi\Importing\Infrastructure\Persistence\Eloquent\ImportJobModel;
use ImovelZapAi\Tenancy\Domain\Enums\TenantStatus;
use ImovelZapAi\Tenancy\Infrastructure\Persistence\Eloquent\TenantModel;
use ImovelZapAi\Tenancy\Support\TenantContext;

final class AnalyzeImportAction
{
    public function __construct(
        private readonly VrSyncParserInterface $parser,
        private readonly ImportListingValidator $validator,
        private readonly ImportMappingResolver $mappingResolver,
        private readonly ImportFileStorage $storage,
        private readonly ImportLogWriter $logWriter,
    ) {}

    public function execute(string $importJobId, string $tenantId): ImportJobModel
    {
        TenantContext::set($tenantId);

        $tenant = TenantModel::query()->find($tenantId);
        if ($tenant === null || $tenant->status !== TenantStatus::Active) {
            throw ImportException::tenantInactive();
        }

        $job = ImportJobModel::query()
            ->where('tenant_id', $tenantId)
            ->whereKey($importJobId)
            ->with('file')
            ->firstOrFail();

        try {
            $file = $job->file;
            if ($file === null) {
                throw ImportException::fileMissing();
            }

            $this->storage->verifyHash($file->path, $file->sha256);
            $absolute = $this->storage->absolutePath($file->path);

            $this->logWriter->write($tenantId, $job->id, ImportLogEvent::AnalysisStarted);

            $parseResult = $this->parser->parse($absolute);
            $validationIssues = $this->validator->validate($parseResult->properties);
            $issues = array_merge($parseResult->issues, $validationIssues);

            $resolved = $this->mappingResolver->resolve($parseResult->properties, $job->mapping ?? []);

            $errorListingIndexes = [];
            $warningListingIndexes = [];
            foreach ($issues as $issue) {
                if ($issue->listingIndex === null) {
                    continue;
                }
                if ($issue->severity === ImportIssueSeverity::Error) {
                    $errorListingIndexes[$issue->listingIndex] = true;
                } else {
                    $warningListingIndexes[$issue->listingIndex] = true;
                }
            }

            $total = count($parseResult->properties);
            $errorCount = count($errorListingIndexes);
            $warningCount = count($warningListingIndexes);
            $valid = max(0, $total - $errorCount);

            $missingPhotos = 0;
            foreach ($parseResult->properties as $property) {
                if ($property->media === []) {
                    $missingPhotos++;
                }
            }

            DB::transaction(function () use ($job, $tenantId, $issues, $resolved, $total, $valid, $errorCount, $warningCount, $missingPhotos): void {
                ImportErrorModel::query()->where('import_job_id', $job->id)->delete();

                foreach ($issues as $issue) {
                    $this->persistIssue($tenantId, $job->id, $issue);
                }

                $status = $resolved['ambiguities'] === []
                    ? ImportJobStatus::PreviewReady
                    : ImportJobStatus::AwaitingMapping;

                if ($resolved['ambiguities'] === [] && $errorCount === 0) {
                    $status = ImportJobStatus::Ready;
                } elseif ($resolved['ambiguities'] === []) {
                    $status = ImportJobStatus::PreviewReady;
                }

                $job->forceFill([
                    'status' => $status,
                    'total_listings' => $total,
                    'valid_listings' => $valid,
                    'error_listings' => $errorCount,
                    'warning_listings' => $warningCount,
                    'mapping' => $resolved['mapping'],
                    'preview_summary' => [
                        'total' => $total,
                        'valid' => $valid,
                        'with_errors' => $errorCount,
                        'with_warnings' => $warningCount,
                        'missing_photos' => $missingPhotos,
                        'unknown_neighborhoods' => $resolved['unknown_neighborhoods'],
                        'ambiguities' => $resolved['ambiguities'],
                    ],
                    'preview_ready_at' => now(),
                    'failure_message' => null,
                ])->save();
            });

            $this->logWriter->write(
                $tenantId,
                $job->id,
                ImportLogEvent::AnalysisCompleted,
                message: 'Análise concluída.',
            );
        } catch (\Throwable $e) {
            $job->forceFill([
                'status' => ImportJobStatus::Failed,
                'failure_message' => $e->getMessage(),
                'completed_at' => now(),
            ])->save();

            $this->logWriter->write(
                $tenantId,
                $job->id,
                ImportLogEvent::ImportFailed,
                message: $e->getMessage(),
            );

            throw $e;
        } finally {
            TenantContext::clear();
        }

        return $job->fresh(['file', 'errors']);
    }

    private function persistIssue(string $tenantId, string $jobId, ParseIssue $issue): void
    {
        ImportErrorModel::query()->create([
            'tenant_id' => $tenantId,
            'import_job_id' => $jobId,
            'severity' => $issue->severity,
            'code' => $issue->code,
            'external_id' => $issue->externalId,
            'listing_index' => $issue->listingIndex,
            'path' => $issue->path,
            'message' => $issue->message,
            'resolution' => null,
            'status' => 'open',
            'context' => $issue->context,
        ]);
    }
}
