<?php

namespace App\Services\Archiving;

use App\Models\ArchiveRun;
use App\Models\ArchiveSetting;
use App\Models\Contract;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class ArchiveExecutor
{
    /**
     * Execute a single archive setting. Returns summary counts keyed by table.
     */
    public function executeSetting(ArchiveSetting $setting, ?array $context = null, ?int $userId = null, ?ArchiveRun $existingRun = null): array
    {
        if (! $setting->enabled) {
            return [];
        }

        $results = [];
        $run = $existingRun;
        $started = now();
        $startedHr = microtime(true); // high-resolution start for accurate duration
        if (! $run) {
            $run = ArchiveRun::create([
                'archive_setting_id' => $setting->id,
                'user_id' => $userId,
                'status' => 'running',
                'context' => $context,
                'started_at' => $started,
            ]);
        }
        $entities = $setting->entities ?? [];
        if (! is_array($entities)) {
            return [];
        }

        // Flatten entities: UI stores a single focus entity with a 'related' array.
        $flat = [];
        foreach ($entities as $entityDef) {
            if (! is_array($entityDef)) {
                continue;
            }
            if (! empty($entityDef['table'])) {
                // Mark first / focus explicitly if not set
                if (! array_key_exists('focus', $entityDef)) {
                    // Consider focus if its table matches a known focus (contracts, client_cases)
                    $entityDef['focus'] = in_array($entityDef['table'], ['contracts', 'client_cases']);
                }
                $flat[] = $entityDef;
            }
            if (! empty($entityDef['related']) && is_array($entityDef['related'])) {
                foreach ($entityDef['related'] as $rel) {
                    if (! is_string($rel) || $rel === $entityDef['table']) {
                        continue;
                    }
                    $flat[] = [
                        'table' => $rel,
                        'focus' => false,
                    ];
                }
            }
        }
        if (! empty($flat)) {
            $entities = $flat;
        }

        // dd($entities);

        foreach ($entities as $entityDef) {
            $rawTable = $entityDef['table'] ?? null;
            if (! $rawTable) {
                continue;
            }
            $chain = explode('.', $rawTable);
            $table = end($chain); // physical table name assumed last segment
            $singularToPlural = (array) config('archiving.singular_plural', []);
            if (isset($singularToPlural[$table]) && Schema::hasTable($singularToPlural[$table])) {
                $table = $singularToPlural[$table];
            }
            if (! $table || ! Schema::hasTable($table)) {
                continue;
            }

            // Conditions ignored (simplified mode)
            $soft = (bool) $setting->soft; // soft flag remains relevant for archive
            $reactivate = (bool) ($setting->reactivate ?? false);

            $batchSize = (int) ($setting->options['batch_size'] ?? 500);
            if ($batchSize < 1) {
                $batchSize = 500;
            }

            $affectedTotal = 0;
            // Process in batches to avoid locking large tables
            while (true) {
                $query = DB::table($table)->whereNull('deleted_at');
                if (Schema::hasColumn($table, 'active') && ! $reactivate) {
                    $query->where('active', 1);
                }
                // Apply context filters or chain derived filters
                $filterApplied = $this->applyContextFilters($query, $context, $table, (bool) ($entityDef['focus'] ?? false), $chain, $rawTable);
                // If context provided but no filter could be applied and this is not the focus entity, skip to avoid whole-table archiving.
                if ($context && ! $filterApplied && empty($entityDef['focus'])) {
                    break;
                }

                $ids = $query->limit($batchSize)->pluck('id');
                if ($ids->isEmpty()) {
                    break;
                }

                DB::transaction(function () use ($table, $ids, $soft, $reactivate, &$affectedTotal) {
                    if ($reactivate) {
                        // Reactivation path
                        if (Schema::hasColumn($table, 'active')) {
                            DB::table($table)
                                ->whereIn('id', $ids)
                                ->update(['active' => 1, 'updated_at' => now(), 'deleted_at' => null]);
                            $affectedTotal += $ids->count();
                        } elseif (Schema::hasColumn($table, 'deleted_at')) {
                            DB::table($table)
                                ->whereIn('id', $ids)
                                ->update(['deleted_at' => null, 'updated_at' => now()]);
                            $affectedTotal += $ids->count();
                        }
                    } else {
                        // Archiving path
                        if ($soft && Schema::hasColumn($table, 'active')) {
                            DB::table($table)
                                ->whereIn('id', $ids)
                                ->update([
                                    'active' => 0,
                                    'updated_at' => now(),
                                ]);
                            $affectedTotal += $ids->count();
                        } elseif ($soft && Schema::hasColumn($table, 'deleted_at')) {
                            DB::table($table)
                                ->whereIn('id', $ids)
                                ->update([
                                    'deleted_at' => now(),
                                    'updated_at' => now(),
                                ]);
                            $affectedTotal += $ids->count();
                        } else {
                            // Hard delete
                            $affectedTotal += DB::table($table)->whereIn('id', $ids)->delete();
                        }
                    }
                });

                if ($ids->count() < $batchSize) {
                    break; // last batch
                }
            }

            if ($affectedTotal > 0) {
                $results[$table] = $affectedTotal;
            }
        }

        try {
            if (! empty($results)) {
                Log::info('ArchiveExecutor results', [
                    'setting_id' => $setting->id,
                    'results' => $results,
                ]);
            }
            $finished = now();
            $durationMs = (int) max(0, round((microtime(true) - $startedHr) * 1000));
            $run->update([
                'status' => 'success',
                'counts' => $results,
                'finished_at' => $finished,
                'duration_ms' => $durationMs,
            ]);
        } catch (\Throwable $e) {
            $durationMs = (int) max(0, round((microtime(true) - $startedHr) * 1000));
            try {
                $run->update([
                    'status' => 'error',
                    'message' => $e->getMessage(),
                    'finished_at' => now(),
                    'duration_ms' => $durationMs,
                ]);
            } catch (\Throwable $ignored) {
                // swallow secondary failure to avoid masking original exception
            }
            throw $e;
        }

        return $results;
    }

    /**
     * Context filters allow scoping execution (e.g., only a given contract id) during manual per-record archive.
     * Expected keys in $context: contract_id, client_case_id, account_id etc.
     */
    protected function applyContextFilters(Builder $query, ?array $context, string $table, bool $isFocus, array $chain = [], ?string $raw = null): bool
    {
        $applied = false;
        if (! $context) {
            return $applied;
        }
        foreach ($context as $key => $value) {
            if ($value === null) {
                continue;
            }
            // Special precedence: for contracts table, if contract_id provided, always scope to that single id
            if ($table === 'contracts' && $key === 'contract_id') {
                $query->where('id', $value);
                $applied = true;

                // Do NOT allow broader filters (e.g., client_case_id) to widen the scope afterward
                continue;
            }
            if (Schema::hasColumn($query->from, $key) && ! ($table === 'contracts' && $key === 'client_case_id' && isset($context['contract_id']))) {
                // For contracts table: if contract_id present, skip client_case_id to avoid capturing all contracts of the case
                $query->where($key, $value);
                $applied = true;
            }
        }

        // Support polymorphic documents (documentable_id/type) for contract context
        if (! $applied && isset($context['contract_id']) && Schema::hasColumn($table, 'documentable_type') && Schema::hasColumn($table, 'documentable_id')) {
            $query->where('documentable_type', \App\Models\Contract::class)->where('documentable_id', $context['contract_id']);
            $applied = true;
        }

        // Fallback: for the focus entity contracts table using contract_id context
        if (! $applied && $isFocus && isset($context['contract_id']) && $table === 'contracts') {
            $query->where('id', $context['contract_id']);
            $applied = true;
        }

        // Chain-based inference (dot notation) limited strictly to declared chain segments.
        // Examples:
        //  - account.payments => resolve payments by account_id from context (if available via contract->account)
        //  - account.bookings => same pattern
        //  - contracts.documents => already handled by polymorphic logic above
        if (! $applied && ! empty($chain) && count($chain) > 1) {
            // We only support a limited mapping derived from context keys, no dynamic relationship traversal.
            // Supported patterns:
            //   account.payments => requires account_id (contracts focus)
            //   account.bookings => requires account_id (contracts focus)
            //   contracts.account => requires contract_id, maps to accounts.contract_id
            //   contracts.account.payments => requires contract_id then account_id (pre-provided in context)
            //   contracts.account.bookings => same as above
            // Additional patterns can be appended cautiously.
            $pattern = implode('.', $chain);
            switch ($pattern) {
                case 'account.payments':
                case 'account.bookings':
                    if (isset($context['account_id']) && Schema::hasColumn($table, 'account_id')) {
                        $query->where('account_id', $context['account_id']);
                        $applied = true;
                    }
                    break;
                case 'contracts.account':
                    if (isset($context['contract_id']) && $table === 'accounts' && Schema::hasColumn('accounts', 'contract_id')) {
                        $query->where('contract_id', $context['contract_id']);
                        $applied = true;
                    }
                    break;
                case 'contracts.account.payments':
                case 'contracts.account.bookings':
                    // Prefer direct account_id context if present; if not, we cannot safely infer without querying
                    if (isset($context['account_id']) && Schema::hasColumn($table, 'account_id')) {
                        $query->where('account_id', $context['account_id']);
                        $applied = true;
                    } elseif (isset($context['contract_id']) && Schema::hasColumn($table, 'account_id') && Schema::hasTable('accounts')) {
                        // Derive account ids for this contract in a subquery (limited, safe scope)
                        $accountIds = DB::table('accounts')->where('contract_id', $context['contract_id'])->pluck('id');
                        if ($accountIds->isNotEmpty()) {
                            $query->whereIn('account_id', $accountIds);
                            $applied = true;
                        }
                    }
                    break;
                case 'contracts.documents':
                    // already covered by polymorphic; if not yet applied, mimic
                    if (isset($context['contract_id']) && Schema::hasColumn($table, 'documentable_type') && Schema::hasColumn($table, 'documentable_id')) {
                        $query->where('documentable_type', \App\Models\Contract::class)
                            ->where('documentable_id', $context['contract_id']);
                        $applied = true;
                    }
                    break;
            }
        }

        return $applied;
    }
}
