<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreContractRequest;
use App\Http\Requests\UpdateContractRequest;
use App\Models\Client;
use App\Models\ClientCase;
use App\Models\Contract;
use App\Models\Document;
use App\Models\Segment;
use App\Services\Documents\DocumentStreamService;
use App\Services\ReferenceDataCache;
use App\Services\Sms\SmsService;
use Exception;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use Inertia\Inertia;

class ClientCaseContoller extends Controller
{
    public function __construct(
        protected ReferenceDataCache $referenceCache,
        protected DocumentStreamService $documentStream,
        protected \App\Services\ClientCaseDataService $caseDataService
    ) {}

    /**
     * Display a listing of the resource.
     */
    public function index(ClientCase $clientCase, Request $request)
    {
        $search = $request->input('search');
        $from = $this->normalizeDate($request->input('from'));
        $to = $this->normalizeDate($request->input('to'));
        $clientFilter = collect(explode(',', (string) $request->input('clients')))
            ->filter()
            ->map(fn ($value) => (int) $value)
            ->filter(fn ($value) => $value > 0)
            ->unique()
            ->values();

        $perPage = $this->resolvePerPage($request);

        $query = $clientCase::query()
            ->select('client_cases.*')
            ->when($search, function ($que) use ($search) {
                $que->join('person', 'person.id', '=', 'client_cases.person_id')
                    ->where('person.full_name', 'ilike', '%'.$search.'%')
                    ->groupBy('client_cases.id');
            })
            ->where('client_cases.active', 1)
            ->leftJoin('contracts', function ($join) {
                $join->on('contracts.client_case_id', '=', 'client_cases.id')
                    ->whereNull('contracts.deleted_at');
            })
            ->leftJoin('contract_segment', function ($join) {
                $join->on('contract_segment.contract_id', '=', 'contracts.id')
                    ->where('contract_segment.active', true);
            })
            ->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
            ->when($clientFilter->isNotEmpty(), function ($que) use ($clientFilter) {
                $que->whereIn('client_cases.client_id', $clientFilter->all());
            })
            ->when($from, function ($que) use ($from) {
                $que->whereDate('client_cases.created_at', '>=', $from);
            })
            ->when($to, function ($que) use ($to) {
                $que->whereDate('client_cases.created_at', '<=', $to);
            })
            ->groupBy('client_cases.id')
            ->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count')
            ->selectRaw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum')
            ->with(['person.client', 'client.person'])
            ->orderByDesc('client_cases.created_at');

        return Inertia::render('Cases/Index', [
            'client_cases' => $query
                ->paginate($perPage, ['*'], 'clientCasesPage')
                ->withQueryString(),
            'filters' => [
                'search' => $search,
                'from' => $from,
                'to' => $to,
                'clients' => $clientFilter->map(fn ($value) => (string) $value)->all(),
                'perPage' => $perPage,
            ],
            'clients' => Client::query()
                ->select(['clients.id', 'person.full_name as name'])
                ->join('person', 'person.id', '=', 'clients.person_id')
                ->orderBy('person.full_name')
                ->get()
                ->map(fn ($client) => [
                    'id' => (int) $client->id,
                    'name' => (string) ($client->name ?? ''),
                ]),
        ]);
    }

    private function resolvePerPage(Request $request): int
    {
        $allowed = [10, 15, 25, 50, 100];

        $perPage = (int) $request->integer('perPage', 15);

        return in_array($perPage, $allowed, true) ? $perPage : 15;
    }

    private function normalizeDate(?string $value): ?string
    {
        if (! $value) {
            return null;
        }

        try {
            return Carbon::parse($value)->toDateString();
        } catch (\Throwable) {
            return null;
        }
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
        $cuuid = $request->input('client_uuid');

        $client = \App\Models\Client::where('uuid', $cuuid)->firstOrFail();

        if (isset($client->id)) {

            \DB::transaction(function () use ($request, $client) {
                $pq = $request->input('person');

                $person = $client->person()->create([
                    'nu' => rand(100000, 200000),
                    'first_name' => $pq['first_name'],
                    'last_name' => $pq['last_name'],
                    'full_name' => $pq['full_name'],
                    'gender' => null,
                    'birthday' => null,
                    'tax_number' => $pq['tax_number'],
                    'social_security_number' => $pq['social_security_number'],
                    'description' => $pq['description'],
                    'group_id' => 2,
                    'type_id' => 1,
                ]);

                $person->addresses()->create([
                    'address' => $pq['address']['address'],
                    'country' => $pq['address']['country'],
                    'type_id' => $pq['address']['type_id'],
                ]);

                $person->phones()->create([
                    'nu' => $pq['phone']['nu'],
                    'country_code' => $pq['phone']['country_code'],
                    'type_id' => $pq['phone']['type_id'],
                ]);

                $person->clientCase()->create([
                    'client_id' => $client->id,
                ]);
            });
        }

        return back()->with('success', 'Client created.')->with('flash_method', 'POST');
    }

    public function storeContract(ClientCase $clientCase, StoreContractRequest $request)
    {

        \DB::transaction(function () use ($request, $clientCase) {

            // Create contract
            $contract = $clientCase->contracts()->create([
                'reference' => $request->input('reference'),
                'start_date' => \App\Services\DateNormalizer::toDate($request->input('start_date')),
                'type_id' => $request->input('type_id'),
                'description' => $request->input('description'),
            ]);

            // Note: Contract config auto-application is handled in Contract model created hook.

            // Optionally create related account with amounts and/or type
            $initial = $request->input('initial_amount');
            $balance = $request->input('balance_amount');
            $accountTypeId = $request->input('account_type_id');
            if (! is_null($initial) || ! is_null($balance) || ! is_null($accountTypeId)) {
                $contract->account()->create([
                    'type_id' => $accountTypeId,
                    'initial_amount' => $initial ?? 0,
                    'balance_amount' => $balance ?? 0,
                ]);
            }

        });

        // Preserve segment filter if present
        $segment = request('segment');

        return back()->with('success', 'Contract created.')->with('flash_method', 'POST');
    }

    public function updateContract(ClientCase $clientCase, string $uuid, UpdateContractRequest $request)
    {
        $contract = Contract::where('uuid', $uuid)->firstOrFail();
        if (! $contract->active) {
            return back()->with('warning', __('contracts.edit_not_allowed_archived'));
        }

        $balanceChanged = false;
        $oldBalance = null;
        $newBalance = null;

        \DB::transaction(function () use ($request, $contract, &$balanceChanged, &$oldBalance, &$newBalance) {
            $contract->update([
                'reference' => $request->input('reference'),
                'type_id' => $request->input('type_id'),
                'description' => $request->input('description'),
                'start_date' => $request->filled('start_date') ? \App\Services\DateNormalizer::toDate($request->input('start_date')) : $contract->start_date,
            ]);

            $initial = $request->input('initial_amount');
            // Use has() to distinguish between an omitted field and an explicit 0 / null intent
            $balanceFieldPresent = $request->has('balance_amount');
            $balance = $balanceFieldPresent ? $request->input('balance_amount') : null;
            // Always allow updating existing account even if only balance set to 0 (or unchanged) so user can correct it.
            $hasType = $request->has('account_type_id');
            $shouldUpsertAccount = ($contract->account()->exists()) || (! is_null($initial)) || $balanceFieldPresent || $hasType;
            if ($shouldUpsertAccount) {
                $accountData = [];
                // Track old balance before applying changes
                $currentAccount = $contract->account; // newest (latestOfMany)
                if (! is_null($initial)) {
                    $accountData['initial_amount'] = $initial;
                }
                // If the balance field was present in the request payload we always apply it (allow setting to 0)
                if ($balanceFieldPresent) {
                    // Allow explicitly setting to 0, fallback to 0 if null provided
                    $accountData['balance_amount'] = $balance ?? 0;
                }
                if ($request->has('account_type_id')) {
                    $accountData['type_id'] = $request->input('account_type_id');
                }
                if ($currentAccount) {
                    $oldBalance = (float) $currentAccount->balance_amount;
                    $currentAccount->update($accountData);
                    if (array_key_exists('balance_amount', $accountData)) {
                        $currentAccount->forceFill(['balance_amount' => $accountData['balance_amount']])->save();
                        $freshBal = (float) optional($currentAccount->fresh())->balance_amount;
                        if ((float) $freshBal !== (float) $accountData['balance_amount']) {
                            \DB::table('accounts')
                                ->where('id', $currentAccount->id)
                                ->update(['balance_amount' => $accountData['balance_amount'], 'updated_at' => now()]);
                            $freshBal = (float) optional($currentAccount->fresh())->balance_amount;
                        }
                        $newBalance = $freshBal;
                        if ($oldBalance !== $freshBal) {
                            $balanceChanged = true;
                        }
                    } else {
                        $freshBal = (float) optional($currentAccount->fresh())->balance_amount;
                    }
                } else {
                    $accountData = array_merge(['initial_amount' => 0, 'balance_amount' => 0], $accountData);
                    $created = $contract->account()->create($accountData);
                    $freshBal = (float) optional($created->fresh())->balance_amount;
                }
            }

        });

        // Fire activity if balance changed and settings require it
        if ($balanceChanged) {
            $contractSetting = \App\Models\ContractSetting::query()->first();
            if ($contractSetting && $contractSetting->create_activity_on_balance_change) {
                $note = str_replace(
                    ['{old_balance}', '{new_balance}', '{currency}'],
                    [number_format($oldBalance, 2, '.', ''), number_format($newBalance, 2, '.', ''), 'EUR'],
                    $contractSetting->activity_note_template ?? ''
                );
                \App\Models\Activity::query()->create([
                    'due_date' => null,
                    'amount' => $newBalance,
                    'note' => $note,
                    'action_id' => $contractSetting->default_action_id,
                    'decision_id' => $contractSetting->default_decision_id,
                    'client_case_id' => $contract->client_case_id,
                    'contract_id' => $contract->id,
                ]);
            }
        }

        // Preserve segment filter if present
        $segment = request('segment');

        return back()->with('success', 'Contract updated.')->with('flash_method', 'PUT');
    }

    /**
     * Debug endpoint: list all account rows for a contract (only in debug mode).
     */
    public function debugContractAccounts(ClientCase $clientCase, string $uuid, Request $request)
    {
        abort_unless(config('app.debug'), 404);
        $contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail(['id', 'uuid', 'reference']);
        $accounts = \DB::table('accounts')
            ->where('contract_id', $contract->id)
            ->orderBy('id')
            ->get(['id', 'contract_id', 'initial_amount', 'balance_amount', 'type_id', 'created_at', 'updated_at']);

        return response()->json([
            'contract' => $contract,
            'accounts' => $accounts,
            'count' => $accounts->count(),
        ]);
    }

    public function storeActivity(ClientCase $clientCase, Request $request)
    {
        try {
            $attributes = $request->validate([
                'due_date' => 'nullable|date',
                'call_back_at' => 'nullable|date_format:Y-m-d H:i:s|after_or_equal:now',
                'amount' => 'nullable|decimal:0,4',
                'note' => 'nullable|string',
                'action_id' => 'exists:\App\Models\Action,id',
                'decision_id' => 'exists:\App\Models\Decision,id',
                'contract_uuid' => 'nullable|uuid',
                'contract_uuids' => 'nullable|array',
                'contract_uuids.*' => 'uuid',
                'create_for_all_contracts' => 'nullable|boolean',
                'phone_view' => 'nullable|boolean',
                'send_auto_mail' => 'sometimes|boolean',
                'attachment_document_ids' => 'sometimes|array',
                'attachment_document_ids.*' => 'integer',
            ]);

            $isPhoneView = $attributes['phone_view'] ?? false;
            $createForAll = $attributes['create_for_all_contracts'] ?? false;
            $contractUuids = $attributes['contract_uuids'] ?? [];

            // Determine which contracts to process
            $contractIds = [];
            if ($createForAll && ! empty($contractUuids)) {
                // Get all contract IDs from the provided UUIDs
                $contracts = Contract::withTrashed()
                    ->whereIn('uuid', $contractUuids)
                    ->where('client_case_id', $clientCase->id)
                    ->get();
                $contractIds = $contracts->pluck('id')->toArray();
            } elseif (! empty($contractUuids) && isset($contractUuids[0])) {
                // Single contract mode
                $contract = Contract::withTrashed()
                    ->where('uuid', $contractUuids[0])
                    ->where('client_case_id', $clientCase->id)
                    ->first();
                if ($contract) {
                    $contractIds = [$contract->id];
                }
            } elseif (! empty($attributes['contract_uuid'])) {
                // Legacy single contract_uuid support
                $contract = Contract::withTrashed()
                    ->where('uuid', $attributes['contract_uuid'])
                    ->where('client_case_id', $clientCase->id)
                    ->first();
                if ($contract) {
                    $contractIds = [$contract->id];
                }
            }

            // If no contracts specified, create a single activity without contract
            if (empty($contractIds)) {
                $contractIds = [null];
            }

            $createdActivities = [];
            $sendFlag = (bool) ($attributes['send_auto_mail'] ?? true);

            // Disable auto mail if creating activities for multiple contracts
            if ($sendFlag && count($contractIds) > 1) {
                $sendFlag = false;
                logger()->info('Auto mail disabled: multiple contracts selected', ['contract_count' => count($contractIds)]);
            }

            foreach ($contractIds as $contractId) {
                // Create activity
                $row = $clientCase->activities()->create([
                    'due_date' => $attributes['due_date'] ?? null,
                    'call_back_at' => $attributes['call_back_at'] ?? null,
                    'amount' => $attributes['amount'] ?? null,
                    'note' => $attributes['note'] ?? null,
                    'action_id' => $attributes['action_id'],
                    'decision_id' => $attributes['decision_id'],
                    'contract_id' => $contractId,
                ]);

                $createdActivities[] = $row;

                if ($isPhoneView && $contractId) {
                    $contract = Contract::find($contractId);
                    if ($contract) {
                        $fieldJob = $contract->fieldJobs()
                            ->whereNull('completed_at')
                            ->whereNull('cancelled_at')
                            ->where('assigned_user_id', \Auth::id())
                            ->orderByDesc('id')
                            ->first();

                        if ($fieldJob) {
                            $fieldJob->update([
                                'added_activity' => true,
                                'last_activity' => $row->created_at,
                            ]);
                        }
                    }
                }

                logger()->info('Activity successfully inserted', array_merge($attributes, ['contract_id' => $contractId]));

                // Auto mail dispatch (best-effort)
                try {
                    $row->load(['decision', 'clientCase.client.person', 'clientCase.person', 'contract']);
                    // Filter attachments to those belonging to the selected contract
                    $attachmentIds = collect($attributes['attachment_document_ids'] ?? [])
                        ->filter()
                        ->map(fn ($v) => (int) $v)
                        ->values();
                    $validAttachmentIds = collect();
                    if ($attachmentIds->isNotEmpty() && $contractId) {
                        $validAttachmentIds = Document::query()
                            ->where('documentable_type', Contract::class)
                            ->where('documentable_id', $contractId)
                            ->whereIn('id', $attachmentIds)
                            ->pluck('id');
                        $validAttachmentIds = Document::query()
                            ->where('documentable_type', Contract::class)
                            ->where('documentable_id', $contractId)
                            ->whereIn('id', $attachmentIds)
                            ->pluck('id');
                    }
                    $result = app(\App\Services\AutoMailDispatcher::class)->maybeQueue($row, $sendFlag, [
                        'attachment_ids' => $validAttachmentIds->all(),
                    ]);
                    if (($result['skipped'] ?? null) === 'missing-contract' && $sendFlag) {
                        // If template requires contract and user attempted to send, surface a validation message
                        logger()->warning('Email not queued: required contract is missing for the selected template.');
                    }
                    if (($result['skipped'] ?? null) === 'no-recipients' && $sendFlag) {
                        logger()->warning('Email not queued: no eligible client emails to receive auto mails.');
                    }
                } catch (\Throwable $e) {
                    // Do not fail activity creation due to mailing issues
                    logger()->warning('Auto mail dispatch failed: '.$e->getMessage());
                }
            }

            $activityCount = count($createdActivities);
            $successMessage = $activityCount > 1
                ? "Successfully created {$activityCount} activities!"
                : 'Successfully created activity!';

            // Stay on the current page (desktop or phone) instead of forcing a redirect to the desktop route.
            // Use 303 to align with Inertia's recommended POST/Redirect/GET behavior.
            return back(303)->with('success', $successMessage)->with('flash_method', 'POST');
        } catch (QueryException $e) {
            logger()->error('Database error occurred:', ['error' => $e->getMessage()]);

            return back()->with('error', 'Failed to insert activity. '.$e->getMessage());
        } catch (Exception $e) {
            logger()->error('An unexpected error occurred:', ['error' => $e->getMessage()]);

            // Return a generic error response
            return back()->with('error', 'An unexpected error occurred. Please try again later.');
        }

    }

    public function deleteActivity(ClientCase $clientCase, \App\Models\Activity $activity, Request $request)
    {
        // Ensure activity belongs to this case
        if ($activity->client_case_id !== $clientCase->id) {
            abort(404);
        }

        \DB::transaction(function () use ($activity) {
            $activity->delete();
        });

        return back()->with('success', 'Activity deleted.');
    }

    public function deleteContract(ClientCase $clientCase, string $uuid, Request $request)
    {
        $contract = Contract::where('uuid', $uuid)->firstOrFail();

        // Preserve segment filter if present
        $segment = request('segment');

        return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment])->with('flash_method', 'DELETE');
    }

    public function updateContractSegment(ClientCase $clientCase, string $uuid, Request $request)
    {
        $validated = $request->validate([
            'segment_id' => ['required', 'integer', 'exists:segments,id'],
        ]);

        $contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail();

        // Safety: Disallow segment change if contract archived (inactive)
        if (! $contract->active) {
            return back()->with('warning', __('contracts.segment_change_not_allowed_archived'));
        }

        \DB::transaction(function () use ($contract, $validated) {
            // Deactivate current active relation(s)
            \DB::table('contract_segment')
                ->where('contract_id', $contract->id)
                ->where('active', true)
                ->update(['active' => false]);

            // Attach or update the selected segment as active
            $existing = \DB::table('contract_segment')
                ->where('contract_id', $contract->id)
                ->where('segment_id', $validated['segment_id'])
                ->first();

            if ($existing) {
                \DB::table('contract_segment')
                    ->where('id', $existing->id)
                    ->update(['active' => true, 'updated_at' => now()]);
            } else {
                $contract->segments()->attach($validated['segment_id'], ['active' => true, 'created_at' => now(), 'updated_at' => now()]);
            }
        });

        return back()->with('success', 'Contract segment updated.')->with('flash_method', 'PATCH');
    }

    public function patchContractMeta(ClientCase $clientCase, string $uuid, Request $request)
    {
        $validated = $request->validate([
            'meta' => ['required', 'array'],
        ]);

        $contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail();

        $contract->update([
            'meta' => $validated['meta'],
        ]);

        return back()->with('success', __('Meta podatki so bili posodobljeni.'));
    }

    public function attachSegment(ClientCase $clientCase, Request $request)
    {
        $validated = $request->validate([
            'segment_id' => ['required', 'integer', 'exists:segments,id'],
            'contract_uuid' => ['nullable', 'uuid'],
            'make_active_for_contract' => ['sometimes', 'boolean'],
        ]);

        \DB::transaction(function () use ($clientCase, $validated) {
            // Attach segment to client case if not already attached
            $attached = \DB::table('client_case_segment')
                ->where('client_case_id', $clientCase->id)
                ->where('segment_id', $validated['segment_id'])
                ->first();
            if (! $attached) {
                $clientCase->segments()->attach($validated['segment_id'], ['active' => true]);
            } elseif (! $attached->active) {
                \DB::table('client_case_segment')
                    ->where('id', $attached->id)
                    ->update(['active' => true, 'updated_at' => now()]);
            }

            // Optionally make it active for a specific contract
            if (! empty($validated['contract_uuid']) && ($validated['make_active_for_contract'] ?? false)) {
                $contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->firstOrFail();
                if (! $contract->active) {
                    // Prevent segment activation for archived contract
                    return; // Silent; we still attach to case but do not alter archived contract
                }
                \DB::table('contract_segment')
                    ->where('contract_id', $contract->id)
                    ->where('active', true)
                    ->update(['active' => false]);

                $existing = \DB::table('contract_segment')
                    ->where('contract_id', $contract->id)
                    ->where('segment_id', $validated['segment_id'])
                    ->first();
                if ($existing) {
                    \DB::table('contract_segment')
                        ->where('id', $existing->id)
                        ->update(['active' => true, 'updated_at' => now()]);
                } else {
                    $contract->segments()->attach($validated['segment_id'], ['active' => true, 'created_at' => now(), 'updated_at' => now()]);
                }
            }
        });

        return back()->with('success', 'Segment attached to case.')->with('flash_method', 'PATCH');
    }

    public function storeDocument(ClientCase $clientCase, Request $request)
    {
        $validated = $request->validate([
            'file' => 'required|file|max:25600|mimes:doc,docx,pdf,txt,csv,xls,xlsx,jpeg,png', // 25MB and allowed types
            'name' => 'nullable|string|max:255',
            'description' => 'nullable|string',
            'is_public' => 'sometimes|boolean',
            'contract_uuid' => 'nullable|uuid',
        ]);

        $file = $validated['file'];
        $disk = 'public';
        $contract = null;
        if (! empty($validated['contract_uuid'])) {
            $contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->first();
            /*if ($contract && ! $contract->active) {
                return back()->with('warning', __('contracts.document_not_allowed_archived'));
            }*/
        }
        $directory = $contract
            ? ('contracts/'.$contract->uuid.'/documents')
            : ('cases/'.$clientCase->uuid.'/documents');
        $path = $file->store($directory, $disk);

        $doc = new Document([
            'name' => $validated['name'] ?? pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME),
            'description' => $validated['description'] ?? null,
            'user_id' => optional($request->user())->id,
            'disk' => $disk,
            'path' => $path,
            'file_name' => basename($path),
            'original_name' => $file->getClientOriginalName(),
            'extension' => $file->getClientOriginalExtension(),
            'mime_type' => $file->getMimeType(),
            'size' => $file->getSize(),
            'checksum' => null,
            'is_public' => (bool) ($validated['is_public'] ?? false),
        ]);

        if ($contract) {
            $contract->documents()->save($doc);
        } else {
            $clientCase->documents()->save($doc);
        }

        // Generate preview immediately for Office docs to avoid first-view delay
        $ext = strtolower($doc->extension ?? pathinfo($doc->original_name ?? $doc->file_name, PATHINFO_EXTENSION));
        if (in_array($ext, ['doc', 'docx'])) {
            \App\Jobs\GenerateDocumentPreview::dispatch($doc->id);
        }

        return back()->with('success', 'Document uploaded.')->with('flash_method', 'POST');
    }

    public function updateDocument(ClientCase $clientCase, Document $document, Request $request)
    {
        // Validate that the document being updated is scoped to this case (or one of its contracts).
        // Ensure the document belongs to this case or its contracts
        $belongsToCase = $document->documentable_type === ClientCase::class && $document->documentable_id === $clientCase->id;
        $belongsToContractOfCase = false;
        if ($document->documentable_type === Contract::class) {
            $belongsToContractOfCase = Contract::withTrashed()
                ->where('id', $document->documentable_id)
                ->where('client_case_id', $clientCase->id)
                ->exists();
        }

        if (! ($belongsToCase || $belongsToContractOfCase)) {
            logger()->warning('Document update 404: document not in scope of client case or its contracts', [
                'doc_id' => $document->id,
                'doc_uuid' => $document->uuid,
                'doc_type' => $document->documentable_type,
                'doc_doc_id' => $document->documentable_id,
                'route_case_id' => $clientCase->id,
                'route_case_uuid' => $clientCase->uuid,
            ]);
            abort(404);
        }

        // Strictly validate that provided contract_uuid (when present) belongs to THIS client case.
        // If a different case's contract UUID is provided, return a validation error (422) instead of falling back.
        $validated = $request->validate([
            'name' => 'nullable|string|max:255',
            'description' => 'nullable|string',
            'is_public' => 'sometimes|boolean',
            // Optional reassignment to a contract within the same case
            // Note: empty string explicitly means "move back to case" when the key exists in the request.
            'contract_uuid' => [
                'nullable',
                'uuid',
                \Illuminate\Validation\Rule::exists('contracts', 'uuid')->where(function ($q) use ($clientCase, $request) {
                    // Allow empty string if key exists (handled later) by skipping exists check when empty
                    $incoming = $request->input('contract_uuid');
                    if (is_null($incoming) || $incoming === '') {
                        // Return a condition that always matches something harmless; exists rule is ignored in this case
                        return $q; // no-op, DBAL will still run but empty will be caught by nullable
                    }

                    return $q->where('client_case_id', $clientCase->id);
                }),
            ],
        ]);

        // Basic attribute updates
        $document->name = $validated['name'] ?? $document->name;
        if (array_key_exists('description', $validated)) {
            $document->description = $validated['description'];
        }
        if (array_key_exists('is_public', $validated)) {
            $document->is_public = (bool) $validated['is_public'];
        }

        // Reassign to contract or back to case IF the key is present in the payload (explicit intent).
        if ($request->exists('contract_uuid')) {
            $incoming = $request->input('contract_uuid');
            if ($incoming === '' || is_null($incoming)) {
                // Explicitly move relation back to the case
                $document->documentable_type = ClientCase::class;
                $document->documentable_id = $clientCase->id;
            } else {
                // Safe to resolve within this case due to the validation rule above
                $target = $clientCase->contracts()->where('uuid', $incoming)->firstOrFail(['id', 'uuid', 'active']);
                if (! $target->active) {
                    return back()->with('warning', __('contracts.document_not_allowed_archived'));
                }

                $document->documentable_type = Contract::class;
                $document->documentable_id = $target->id;
            }
        }

        $document->save();

        // Refresh documents list on page
        return back()->with('success', 'Document updated.')->with('flash_method', 'PUT');
    }

    public function viewDocument(ClientCase $clientCase, Document $document, Request $request)
    {
        // Ensure the document belongs to this client case or its contracts
        $belongsToCase = $document->documentable_type === ClientCase::class && $document->documentable_id === $clientCase->id;
        $belongsToContractOfCase = false;
        if ($document->documentable_type === Contract::class) {
            // Include soft-deleted contracts when verifying ownership to this case
            $belongsToContractOfCase = Contract::withTrashed()
                ->where('id', $document->documentable_id)
                ->where('client_case_id', $clientCase->id)
                ->exists();

        }
        if (! ($belongsToCase || $belongsToContractOfCase)) {
            logger()->warning('Document view 404: document does not belong to case or its contracts', [
                'document_id' => $document->id,
                'document_uuid' => $document->uuid,
                'documentable_type' => $document->documentable_type,
                'documentable_id' => $document->documentable_id,
                'client_case_id' => $clientCase->id,
                'client_case_uuid' => $clientCase->uuid,
            ]);
            abort(404);
        }

        return $this->documentStream->stream($document, inline: true);
    }

    public function downloadDocument(ClientCase $clientCase, Document $document, Request $request)
    {
        $belongsToCase = $document->documentable_type === ClientCase::class && $document->documentable_id === $clientCase->id;
        $belongsToContractOfCase = false;
        if ($document->documentable_type === Contract::class) {
            $belongsToContractOfCase = Contract::withTrashed()
                ->where('id', $document->documentable_id)
                ->where('client_case_id', $clientCase->id)
                ->exists();
        }
        if (! ($belongsToCase || $belongsToContractOfCase)) {
            logger()->warning('Document download 404: document does not belong to case or its contracts', [
                'document_id' => $document->id,
                'document_uuid' => $document->uuid,
                'documentable_type' => $document->documentable_type,
                'documentable_id' => $document->documentable_id,
                'client_case_id' => $clientCase->id,
                'client_case_uuid' => $clientCase->uuid,
            ]);
            abort(404);
        }

        return $this->documentStream->stream($document, inline: false);
    }

    /**
     * View a contract document using contract route binding.
     */
    public function viewContractDocument(Contract $contract, Document $document, Request $request)
    {
        // Ensure the document belongs to this contract (including trashed docs)
        $belongs = $document->documentable_type === Contract::class && $document->documentable_id === $contract->id;
        if (! $belongs) {
            abort(404);
        }

        return $this->documentStream->stream($document, inline: true);
    }

    /**
     * Download a contract document using contract route binding.
     */
    public function downloadContractDocument(Contract $contract, Document $document, Request $request)
    {
        $belongs = $document->documentable_type === Contract::class && $document->documentable_id === $contract->id;
        if (! $belongs) {
            abort(404);
        }

        return $this->documentStream->stream($document, inline: false);
    }

    /**
     * Display the specified resource.
     */
    public function show(ClientCase $clientCase)
    {
        $case = $clientCase::with([
            'person' => fn ($que) => $que->with(['addresses', 'phones', 'emails', 'bankAccounts', 'client']),
        ])->where('active', 1)->findOrFail($clientCase->id);

        $types = [
            'address_types' => $this->referenceCache->getAddressTypes(),
            'phone_types' => $this->referenceCache->getPhoneTypes(),
        ];

        $segmentId = request()->integer('segment');
        $currentSegment = null;
        if (! empty($segmentId)) {
            $currentSegment = \App\Models\Segment::query()->select('id', 'name')->find($segmentId);
        }

        // Get contracts using service
        $contracts = $this->caseDataService->getContracts($case, $segmentId);
        $contractIds = collect($contracts)->pluck('id')->all();

        // Get activities using service
        $activitiesPerPage = request()->integer('activities_per_page', 15);
        $encodedFilters = request()->input('filter_activities');
        $activities = $this->caseDataService->getActivities($case, $segmentId, $encodedFilters, $contractIds, $activitiesPerPage);

        // Get documents using service
        $contractsPerPage = request()->integer('documentsPerPage', 15);
        $documents = $this->caseDataService->getDocuments($case, $contractIds, $contractsPerPage);

        // Get archive metadata using service

        $archiveMeta = $this->caseDataService->getArchiveMeta();

        return Inertia::render('Cases/Show', [
            'client' => $case->client()->with('person', fn ($q) => $q->with(['addresses', 'phones', 'emails', 'bankAccounts', 'client']))->firstOrFail(),
            'client_case' => $case,
            'contracts' => $contracts,
            'documents' => $documents,
        ])->with([
            'contract_doc_templates' => \App\Models\DocumentTemplate::query()
                ->where('active', true)
                ->where('core_entity', 'contract')
                ->orderBy('slug')
                ->get(['id', 'name', 'slug', 'version', 'tokens', 'meta'])
                ->groupBy('slug')
                ->map(fn ($g) => $g->sortByDesc('version')->first())
                ->values(),
            'archive_meta' => $archiveMeta,
            'activities' => $activities,
            'contract_types' => $this->referenceCache->getContractTypes(),
            'account_types' => $this->referenceCache->getAccountTypes(),
            'actions' => \App\Models\Action::query()
                ->with([
                    'decisions' => function ($q) {
                        $q->select('decisions.id', 'decisions.name', 'decisions.color_tag', 'decisions.auto_mail', 'decisions.email_template_id');
                    },
                    'decisions.emailTemplate' => function ($q) {
                        $q->select('id', 'name', 'entity_types', 'allow_attachments');
                    },
                    'decisions.events' => function ($q) {
                        $q->select('events.id', 'events.key', 'events.name');
                    },
                ])
                ->get(['id', 'name', 'color_tag', 'segment_id']),
            'types' => $types,
            'segments' => $case->segments()->wherePivot('active', true)->get(['segments.id', 'segments.name']),
            'all_segments' => Segment::query()->where('active', true)->get(['id', 'name']),
            'current_segment' => $currentSegment,
            'sms_profiles' => \App\Models\SmsProfile::query()
                ->select(['id', 'name', 'default_sender_id'])
                ->where('active', true)
                ->orderBy('name')
                ->get(),
            'sms_senders' => \App\Models\SmsSender::query()
                ->select(['id', 'profile_id'])
                ->selectRaw('sname as name')
                ->selectRaw('phone_number as phone')
                ->orderBy('sname')
                ->get(),
            'sms_templates' => \App\Models\SmsTemplate::query()
                ->select(['id', 'name', 'content', 'allow_custom_body'])
                ->orderBy('name')
                ->get(),
            'email_templates' => \App\Models\EmailTemplate::query()
                ->select(['id', 'name', 'subject_template', 'text_template', 'action_id', 'decision_id'])
                ->where('active', true)
                ->where('client', false)
                ->orderBy('name')
                ->get(),
            'mail_profiles' => \App\Models\MailProfile::query()
                ->select(['id', 'name'])
                ->where('active', true)
                ->orderBy('priority')
                ->orderBy('name')
                ->get(),
            'auto_mail_decisions' => \App\Models\Decision::query()->where('auto_mail', true)->orderBy('name')->get(['id', 'name']),
        ]);
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }

    /**
     * Delete a document that belongs either directly to the client case or to one of its (even soft deleted) contracts.
     */
    public function deleteDocument(ClientCase $clientCase, Document $document, Request $request)
    {
        // Ownership check: direct case document?
        $belongsToCase = $document->documentable_type === ClientCase::class && $document->documentable_id === $clientCase->id;

        // Or document of a contract that belongs to this case (include trashed contracts)
        $belongsToContractOfCase = false;
        if ($document->documentable_type === Contract::class) {
            $belongsToContractOfCase = Contract::withTrashed()
                ->where('id', $document->documentable_id)
                ->where('client_case_id', $clientCase->id)
                ->exists();
        }

        if (! ($belongsToCase || $belongsToContractOfCase)) {
            abort(404);
        }

        // (Optional future) $this->authorize('delete', $document);

        $document->delete(); // soft delete

        return back()->with('success', 'Document deleted.')->with('flash_method', 'DELETE');
    }

    /**
     * Delete a document accessed through a contract route binding.
     */
    public function deleteContractDocument(Contract $contract, Document $document, Request $request)
    {
        $belongs = $document->documentable_type === Contract::class && $document->documentable_id === $contract->id;
        if (! $belongs) {
            abort(404);
        }

        // (Optional future) $this->authorize('delete', $document);

        $document->delete();

        return back()->with('success', 'Document deleted.')->with('flash_method', 'DELETE');
    }

    /**
     * Manually archive a contract (flag active=0) and optionally its immediate financial relations.
     */
    public function archiveContract(ClientCase $clientCase, string $uuid, Request $request)
    {
        $contract = Contract::query()->where('uuid', $uuid)->firstOrFail();
        if ($contract->client_case_id !== $clientCase->id) {
            \Log::warning('Contract not found uuid: {uuid}', ['uuid' => $uuid]);
            abort(404);
        }

        $attr = $request->validate([
            'reactivate' => 'boolean',
        ]);

        $reactivate = $attr['reactivate'] ?? false;

        $setting = \App\Models\ArchiveSetting::query()
            ->where('enabled', true)
            ->whereIn('strategy', ['immediate', 'manual'])
            ->where('reactivate', $reactivate)
            ->orderByDesc('id')
            ->first();

        if (! $setting->exists()) {
            \Log::warning('No archive settings found!');

            return back()->with('warning', 'No settings found');
        }

        // Service archive executor
        $executor = app(\App\Services\Archiving\ArchiveExecutor::class);
        $result = null;

        $context = [
            'contract_id' => $contract->id,
            'client_case_id' => $clientCase->id,
            'account_id' => $contract->account->id ?? null,
        ];

        try {
            $result = $executor->executeSetting($setting, $context, \Auth::id());
        } catch (Exception $e) {
            \Log::error('There was an error executing ArchiveExecutor::executeSetting {msg}', ['msg' => $e->getMessage()]);

            return back()->with('warning', 'Something went wrong!');
        }

        try {
            \DB::transaction(function () use ($contract, $clientCase, $setting, $reactivate) {
                // Create an Activity record logging this archive if an action or decision is tied to any setting
                if ($setting->action_id && $setting->decision_id) {
                    $activityData = [
                        'client_case_id' => $clientCase->id,
                        'action_id' => $setting->action_id,
                        'decision_id' => $setting->decision_id,
                        'note' => ($reactivate)
                            ? "Ponovno aktivirana pogodba $contract->reference"
                            : "Arhivirana pogodba $contract->reference",
                    ];

                    try {
                        \App\Models\Activity::create($activityData);
                    } catch (Exception $e) {
                        \Log::warning('Activity could not be created!');
                    }

                }

                // If any archive setting specifies a segment_id, move the contract to that segment (archive bucket)
                if ($setting->segment_id) {
                    $segmentId = $setting->segment_id;

                    $contract->segments()
                        ->allRelatedIds()
                        ->map(fn (int $val, int|string $key) => $contract->segments()->updateExistingPivot($val, [
                            'active' => false,
                            'updated_at' => now(),
                        ])
                        );

                    if ($contract->attachedSegments()->find($segmentId)->pluck('id')->isNotEmpty()) {
                        $contract->attachedSegments()->updateExistingPivot($segmentId, [
                            'active' => true,
                            'updated_at' => now(),
                        ]);
                    } else {
                        $contract->segments()->attach(
                            $segmentId,
                            [
                                'active' => true,
                                'created_at' => now(),
                                'updated_at' => now(),
                            ]
                        );
                    }
                }

                $contract->fieldJobs()
                    ->whereNull('completed_at')
                    ->whereNull('cancelled_at')
                    ->update([
                        'cancelled_at' => date('Y-m-d'),
                        'updated_at' => now(),
                    ]);
            });
        } catch (Exception $e) {
            \Log::warning('Something went wrong with inserting / updating archive setting partials!');

            return back()->with('warning', 'Something went wrong!');
        }

        return back()->with('success', $reactivate
            ? __('contracts.reactivated')
            : __('contracts.archived')
        );
    }

    /**
     * Archive multiple contracts in a batch operation
     */
    public function archiveBatch(Request $request)
    {
        $validated = $request->validate([
            'contracts' => 'required|array',
            'contracts.*' => 'required|uuid|exists:contracts,uuid',
            'reactivate' => 'boolean',
        ]);

        $reactivate = $validated['reactivate'] ?? false;

        // Get archive setting
        $setting = \App\Models\ArchiveSetting::query()
            ->where('enabled', true)
            ->whereIn('strategy', ['immediate', 'manual'])
            ->where('reactivate', $reactivate)
            ->orderByDesc('id')
            ->first();

        if (! $setting) {
            \Log::warning('No archive settings found for batch archive');

            return back()->with('flash', [
                'error' => 'No archive settings found',
            ]);
        }

        $executor = app(\App\Services\Archiving\ArchiveExecutor::class);
        $successCount = 0;
        $skippedCount = 0;
        $errors = [];

        foreach ($validated['contracts'] as $contractUuid) {
            try {
                $contract = Contract::where('uuid', $contractUuid)->firstOrFail();

                // Skip if contract is already archived (active = 0)
                if (! $contract->active) {
                    $skippedCount++;

                    continue;
                }

                $clientCase = $contract->clientCase;

                $context = [
                    'contract_id' => $contract->id,
                    'client_case_id' => $clientCase->id,
                    'account_id' => $contract->account->id ?? null,
                ];

                // Execute archive setting
                $executor->executeSetting($setting, $context, \Auth::id());

                // Transaction for segment updates and activity logging
                \DB::transaction(function () use ($contract, $clientCase, $setting, $reactivate) {
                    // Create activity log
                    if ($setting->action_id && $setting->decision_id) {
                        $activityData = [
                            'client_case_id' => $clientCase->id,
                            'action_id' => $setting->action_id,
                            'decision_id' => $setting->decision_id,
                            'note' => ($reactivate)
                                ? "Ponovno aktivirana pogodba $contract->reference"
                                : "Arhivirana pogodba $contract->reference",
                        ];

                        try {
                            \App\Models\Activity::create($activityData);
                        } catch (Exception $e) {
                            \Log::warning('Activity could not be created during batch archive');
                        }
                    }

                    // Move to archive segment if specified
                    if ($setting->segment_id) {
                        $segmentId = $setting->segment_id;

                        // Deactivate all current segments
                        $contract->segments()
                            ->allRelatedIds()
                            ->map(fn (int $val) => $contract->segments()->updateExistingPivot($val, [
                                'active' => false,
                                'updated_at' => now(),
                            ]));

                        // Activate archive segment
                        if ($contract->attachedSegments()->find($segmentId)->pluck('id')->isNotEmpty()) {
                            $contract->attachedSegments()->updateExistingPivot($segmentId, [
                                'active' => true,
                                'updated_at' => now(),
                            ]);
                        } else {
                            $contract->segments()->attach($segmentId, [
                                'active' => true,
                                'created_at' => now(),
                                'updated_at' => now(),
                            ]);
                        }
                    }

                    // Cancel pending field jobs
                    $contract->fieldJobs()
                        ->whereNull('completed_at')
                        ->whereNull('cancelled_at')
                        ->update([
                            'cancelled_at' => date('Y-m-d'),
                            'updated_at' => now(),
                        ]);
                });

                $successCount++;
            } catch (Exception $e) {
                \Log::error('Error archiving contract in batch', [
                    'uuid' => $contractUuid,
                    'error' => $e->getMessage(),
                ]);
                $errors[] = [
                    'uuid' => $contractUuid,
                    'error' => $e->getMessage(),
                ];
            }
        }

        if (count($errors) > 0) {
            $message = "Archived $successCount contracts";
            if ($skippedCount > 0) {
                $message .= ", skipped $skippedCount already archived";
            }
            $message .= ', '.count($errors).' failed';

            return back()->with('flash', [
                'error' => $message,
                'details' => $errors,
            ]);
        }

        $message = $reactivate
            ? "Successfully reactivated $successCount contracts"
            : "Successfully archived $successCount contracts";

        if ($skippedCount > 0) {
            $message .= " ($skippedCount already archived)";
        }

        return back()->with('flash', [
            'success' => $message,
        ]);
    }

    /**
     * Emergency: recreate a missing / soft-deleted person for a client case and re-link related data.
     */
    public function emergencyCreatePerson(ClientCase $clientCase, Request $request)
    {
        $oldPersonId = $clientCase->person_id;
        /** @var \App\Models\Person\Person|null $existing */
        $existing = \App\Models\Person\Person::withTrashed()->find($oldPersonId);
        if ($existing && ! $existing->trashed()) {
            return back()->with('flash', [
                'type' => 'info',
                'message' => 'Person already exists ÔÇô emergency creation not needed.',
            ]);
        }

        $data = $request->validate([
            'full_name' => ['nullable', 'string', 'max:255'],
            'first_name' => ['nullable', 'string', 'max:255'],
            'last_name' => ['nullable', 'string', 'max:255'],
            'tax_number' => ['nullable', 'string', 'max:99'],
            'social_security_number' => ['nullable', 'string', 'max:99'],
            'description' => ['nullable', 'string', 'max:500'],
        ]);

        $fullName = $data['full_name'] ?? trim(($data['first_name'] ?? '').' '.($data['last_name'] ?? ''));
        if ($fullName === '') {
            $fullName = 'Unknown Person';
        }

        $newPerson = null;

        \DB::transaction(function () use ($oldPersonId, $clientCase, $fullName, $data, &$newPerson) {
            $newPerson = \App\Models\Person\Person::create([
                'nu' => null,
                'first_name' => $data['first_name'] ?? null,
                'last_name' => $data['last_name'] ?? null,
                'full_name' => $fullName,
                'gender' => null,
                'birthday' => null,
                'tax_number' => $data['tax_number'] ?? null,
                'social_security_number' => $data['social_security_number'] ?? null,
                'description' => $data['description'] ?? 'Emergency recreated person (case)',
                'group_id' => 2,
                'type_id' => 1,
            ]);

            // Re-point related data referencing old person
            $tables = [
                'emails', 'person_phones', 'person_addresses', 'bank_accounts',
            ];
            foreach ($tables as $table) {
                \DB::table($table)->where('person_id', $oldPersonId)->update(['person_id' => $newPerson->id]);
            }

            // Update the client case
            $clientCase->person_id = $newPerson->id;
            $clientCase->save();
        });

        return back()->with('flash', [
            'type' => 'success',
            'message' => 'New person created and case re-linked.',
            'person_uuid' => $newPerson?->uuid,
        ]);
    }

    /**
     * Send an SMS to a specific phone that belongs to the client case person.
     */
    public function sendSmsToPhone(ClientCase $clientCase, Request $request, int $phone_id)
    {
        $validated = $request->validate([
            'message' => ['required', 'string', 'max:1000'],
            'delivery_report' => ['sometimes', 'boolean'],
            'template_id' => ['sometimes', 'nullable', 'integer', 'exists:sms_templates,id'],
            'profile_id' => ['sometimes', 'nullable', 'integer', 'exists:sms_profiles,id'],
            'sender_id' => ['sometimes', 'nullable', 'integer', 'exists:sms_senders,id'],
            'contract_uuid' => ['sometimes', 'nullable', 'uuid'],
        ]);

        // Ensure the phone belongs to the person of this case
        /** @var \App\Models\Person\PersonPhone|null $phone */
        $phone = \App\Models\Person\PersonPhone::query()
            ->where('id', $phone_id)
            ->where('person_id', $clientCase->person_id)
            ->first();

        if (! $phone) {
            abort(404);
        }

        // Resolve explicit profile/sender if provided; otherwise fallback to first active profile and its default sender
        /** @var \App\Models\SmsProfile|null $profile */
        $profile = null;
        /** @var \App\Models\SmsSender|null $sender */
        $sender = null;

        if (! empty($validated['sender_id']) && empty($validated['profile_id'])) {
            // Infer profile from sender if not explicitly provided
            $sender = \App\Models\SmsSender::query()->find($validated['sender_id']);
            if ($sender) {
                $profile = \App\Models\SmsProfile::query()->find($sender->profile_id);
            }
        }
        if (! empty($validated['profile_id'])) {
            $profile = \App\Models\SmsProfile::query()->where('id', $validated['profile_id'])->first();
            if (! $profile) {
                return back()->with('error', 'Izbran SMS profil ne obstaja.');
            }
            if (property_exists($profile, 'active') && ! $profile->active) {
                return back()->with('error', 'Izbran SMS profil ni aktiven.');
            }
        }
        if (! empty($validated['sender_id'])) {
            $sender = \App\Models\SmsSender::query()->find($validated['sender_id']);
            if (! $sender) {
                return back()->with('error', 'Izbran pošiljatelj ne obstaja.');
            }
            if ($profile && (int) $sender->profile_id !== (int) $profile->id) {
                return back()->with('error', 'Izbran pošiljatelj ne pripada izbranemu profilu.');
            }
        }
        if (! $profile) {
            $profile = \App\Models\SmsProfile::query()
                ->where('active', true)
                ->orderByRaw('CASE WHEN default_sender_id IS NULL THEN 1 ELSE 0 END')
                ->orderBy('id')
                ->first();
        }
        if (! $profile) {
            return back()->with('warning', 'Ni aktivnega SMS profila.');
        }
        if (! $sender && ! empty($profile->default_sender_id)) {
            $sender = \App\Models\SmsSender::query()->find($profile->default_sender_id);
        }

        try {
            /** @var \App\Services\Sms\SmsService $sms */
            $sms = app(\App\Services\Sms\SmsService::class);
            // Check available credits before enqueueing (fail-closed)
            try {
                $raw = (string) $sms->getCreditBalance($profile);
                $num = null;
                if ($raw !== '') {
                    $normalized = str_replace(',', '.', trim($raw));
                    if (preg_match('/-?\d+(?:\.\d+)?/', $normalized, $m)) {
                        $num = (float) ($m[0] ?? null);
                    }
                }
                if (! is_null($num) && $num <= 0.0) {
                    return back()->with('error', 'No credits left.');
                }
            } catch (\Throwable $e) {
                \Log::warning('SMS credit balance check failed', [
                    'error' => $e->getMessage(),
                    'profile_id' => $profile->id,
                ]);

                return back()->with('error', 'Unable to verify SMS credits.');
            }

            // Create an activity before sending
            $activityNote = sprintf('Št: %s | Telo: %s', (string) $phone->nu, (string) $validated['message']);
            $activityData = [
                'note' => $activityNote,
                'user_id' => optional($request->user())->id,
            ];
            // If template provided, attach its action/decision to the activity
            if (! empty($validated['template_id'])) {
                $tpl = \App\Models\SmsTemplate::find((int) $validated['template_id']);
                if ($tpl) {
                    $activityData['action_id'] = $tpl->action_id;
                    $activityData['decision_id'] = $tpl->decision_id;
                }
            }
            // Attach contract_id if contract_uuid is provided and belongs to this case
            if (! empty($validated['contract_uuid'])) {
                $contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->first();
                if ($contract) {
                    $activityData['contract_id'] = $contract->id;
                }
            }

            $activity = $clientCase->activities()->create($activityData);

            // Queue the SMS send; pass activity_id so the job can update note on failure and skip creating a new activity
            \App\Jobs\SendSmsJob::dispatch(
                profileId: $profile->id,
                to: (string) $phone->nu,
                content: (string) $validated['message'],
                senderId: $sender?->id,
                countryCode: $phone->country_code ?: null,
                deliveryReport: (bool) ($validated['delivery_report'] ?? false),
                clientReference: null,
                templateId: $validated['template_id'] ?? null,
                clientCaseId: $clientCase->id,
                userId: optional($request->user())->id,
                activityId: $activity?->id,
            );

            return back()->with('success', 'SMS je bil dodan v ─Źakalno vrsto.');
        } catch (\Throwable $e) {
            \Log::warning('SMS enqueue failed', [
                'error' => $e->getMessage(),
                'case_id' => $clientCase->id,
                'phone_id' => $phone_id,
            ]);

            return back()->with('error', 'SMS ni bil dodan v ─Źakalno vrsto.');
        }
    }

    /**
     * Return contracts for the given client case (for SMS dialog dropdown).
     */
    public function listContracts(ClientCase $clientCase)
    {
        $contracts = $clientCase->contracts()
            ->with('account.type')
            ->select('id', 'uuid', 'reference', 'active', 'start_date', 'end_date', 'meta')
            ->latest('id')
            ->get()
            ->map(function ($c) {
                /** @var SmsService $sms */
                $sms = app(SmsService::class);
                $acc = $c->account;
                $initialRaw = $acc?->initial_amount !== null ? (string) $acc->initial_amount : null;
                $balanceRaw = $acc?->balance_amount !== null ? (string) $acc->balance_amount : null;

                return [
                    'uuid' => $c->uuid,
                    'reference' => $c->reference,
                    'active' => (bool) $c->active,
                    'start_date' => (string) ($c->start_date ?? ''),
                    'end_date' => (string) ($c->end_date ?? ''),
                    'meta' => is_array($c->meta) && ! empty($c->meta) ? $this->flattenMeta($c->meta) : null,
                    'account' => $acc ? [
                        'reference' => $acc->reference,
                        'type' => $acc->type?->name,
                        'initial_amount' => $initialRaw !== null ? $sms->formatAmountEu($initialRaw) : null,
                        'balance_amount' => $balanceRaw !== null ? $sms->formatAmountEu($balanceRaw) : null,
                        'initial_amount_raw' => $initialRaw,
                        'balance_amount_raw' => $balanceRaw,
                    ] : null,
                ];
            });

        return response()->json(['data' => $contracts]);
    }

    /**
     * Render an SMS template preview with optional contract/account placeholders filled.
     */
    public function previewSms(ClientCase $clientCase, Request $request, SmsService $sms)
    {
        $validated = $request->validate([
            'template_id' => ['required', 'integer', 'exists:sms_templates,id'],
            'contract_uuid' => ['sometimes', 'nullable', 'uuid'],
        ]);

        /** @var \App\Models\SmsTemplate $template */
        $template = \App\Models\SmsTemplate::findOrFail((int) $validated['template_id']);

        $vars = [];
        $contractUuid = $validated['contract_uuid'] ?? null;
        if ($contractUuid) {
            // Ensure the contract belongs to this client case
            $contract = $clientCase->contracts()->where('uuid', $contractUuid)
                ->with('account.type')
                ->first();
            if ($contract) {
                $vars['contract'] = [
                    'id' => $contract->id,
                    'uuid' => $contract->uuid,
                    'reference' => $contract->reference,
                    'start_date' => (string) ($contract->start_date ?? ''),
                    'end_date' => (string) ($contract->end_date ?? ''),
                ];
                // Include contract.meta as flattened key-value pairs
                if (is_array($contract->meta) && ! empty($contract->meta)) {
                    $vars['contract']['meta'] = $this->flattenMeta($contract->meta);
                }
                if ($contract->account) {
                    $initialRaw = (string) $contract->account->initial_amount;
                    $balanceRaw = (string) $contract->account->balance_amount;
                    $vars['account'] = [
                        'id' => $contract->account->id,
                        'reference' => $contract->account->reference,
                        'initial_amount' => $sms->formatAmountEu($initialRaw),
                        'balance_amount' => $sms->formatAmountEu($balanceRaw),
                        'initial_amount_raw' => $initialRaw,
                        'balance_amount_raw' => $balanceRaw,
                        'type' => $contract->account->type?->name,
                    ];
                }
            }
        }

        $content = $sms->renderContent($template->content, $vars);

        return response()->json([
            'content' => $content,
            'variables' => $vars,
        ]);
    }

    /**
     * Flatten nested meta structure into dot-notation key-value pairs.
     * Extracts 'value' from objects with {title, value, type} structure.
     * Also creates direct access aliases for nested fields (skipping numeric keys).
     */
    /**
     * Render an email template preview with context from the client case.
     */
    public function previewEmailForEmail(ClientCase $clientCase, Request $request, int $email_id): \Illuminate\Http\JsonResponse
    {
        $validated = $request->validate([
            'template_id' => ['required', 'integer', 'exists:email_templates,id'],
            'contract_uuid' => ['sometimes', 'nullable', 'uuid'],
            'body_text' => ['sometimes', 'nullable', 'string', 'max:10000'],
        ]);

        $email = \App\Models\Email::query()
            ->where('id', $email_id)
            ->where('person_id', $clientCase->person_id)
            ->firstOrFail();

        $template = \App\Models\EmailTemplate::findOrFail((int) $validated['template_id']);

        $contract = null;
        if (! empty($validated['contract_uuid'])) {
            $contract = $clientCase->contracts()
                ->where('uuid', $validated['contract_uuid'])
                ->first();
        }

        $ctx = $this->buildCaseEmailContext($clientCase, $contract);
        $ctx['body_text'] = (string) ($validated['body_text'] ?? '');

        $renderer = app(\App\Services\EmailTemplateRenderer::class);
        $rendered = $renderer->render([
            'subject' => (string) $template->subject_template,
            'html' => (string) $template->html_template,
            'text' => (string) $template->text_template,
        ], $ctx);

        return response()->json([
            'subject' => $rendered['subject'] ?? '',
            'html' => (string) ($rendered['html'] ?? ''),
            'has_body_text' => (bool) preg_match('/{{\s*body_text\s*}}/', (string) $template->html_template),
        ]);
    }

    /**
     * Send a (possibly templated) email to a person email address belonging to this case.
     */
    public function sendEmailToEmail(ClientCase $clientCase, Request $request, int $email_id)
    {
        $validated = $request->validate([
            'subject' => ['required', 'string', 'max:255'],
            'html_body' => ['nullable', 'string'],
            'body_text' => ['nullable', 'string', 'max:10000'],
            'template_id' => ['sometimes', 'nullable', 'integer', 'exists:email_templates,id'],
            'mail_profile_id' => ['sometimes', 'nullable', 'integer', 'exists:mail_profiles,id'],
            'contract_uuid' => ['sometimes', 'nullable', 'uuid'],
        ]);

        // Ensure the email belongs to the person of this case
        $email = \App\Models\Email::query()
            ->where('id', $email_id)
            ->where('person_id', $clientCase->person_id)
            ->firstOrFail();

        $to = (string) $email->value;

        /** @var \App\Models\MailProfile|null $mailProfile */
        $mailProfile = ! empty($validated['mail_profile_id'])
            ? \App\Models\MailProfile::query()->where('id', $validated['mail_profile_id'])->where('active', true)->first()
            : \App\Models\MailProfile::query()->where('active', true)->orderBy('priority')->orderBy('id')->first();

        if (! $mailProfile) {
            return back()->with('error', 'Ni aktivnega e-poštnega profila.');
        }

        $contract = null;
        if (! empty($validated['contract_uuid'])) {
            $contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->first();
        }

        $htmlBody = (string) ($validated['html_body'] ?? '');
        $bodyText = (string) ($validated['body_text'] ?? '');

        // Apply {{body_text}} substitution if the html body contains the placeholder
        if ($bodyText !== '' && preg_match('/{{\s*body_text\s*}}/', $htmlBody)) {
            $renderer = app(\App\Services\EmailTemplateRenderer::class);
            $htmlBody = $renderer->applyBodyText($htmlBody, $bodyText, html: true) ?? $htmlBody;
        }

        $subject = (string) $validated['subject'];

        $log = new \App\Models\EmailLog;
        $log->fill([
            'uuid' => (string) \Illuminate\Support\Str::uuid(),
            'template_id' => $validated['template_id'] ?? null,
            'mail_profile_id' => $mailProfile->id,
            'to_email' => $to,
            'to_recipients' => [$to],
            'subject' => $subject,
            'body_html_hash' => $htmlBody !== '' ? hash('sha256', $htmlBody) : null,
            'body_text_preview' => null,
            'embed_mode' => 'base64',
            'status' => \App\Models\EmailLogStatus::Queued,
            'queued_at' => now(),
            'client_id' => $clientCase->client_id,
            'client_case_id' => $clientCase->id,
            'contract_id' => $contract?->id,
            'ip' => $request->ip(),
        ]);
        $log->save();

        $log->body()->create([
            'body_html' => $htmlBody,
            'body_text' => $bodyText,
            'inline_css' => false,
        ]);

        dispatch(new \App\Jobs\SendEmailTemplateJob($log->id));

        // Create activity if template has action/decision
        if (! empty($validated['template_id'])) {
            $template = \App\Models\EmailTemplate::find((int) $validated['template_id']);
            if ($template && ($template->action_id || $template->decision_id)) {
                $activity = $clientCase->activities()->create(array_filter([
                    'contract_id' => $contract?->id,
                    'action_id' => $template->action_id,
                    'decision_id' => $template->decision_id,
                    'note' => 'Poslano: '.$to.($bodyText !== '' ? ' | Vsebina: '.mb_strimwidth($bodyText, 0, 500, '…') : ''),
                    'user_id' => optional($request->user())->id,
                ], fn ($v) => ! is_null($v)));
                $activity->emailLogs()->attach($log->id);
            }
        }

        return back()->with('success', "E-pošta poslana na {$to}.");
    }

    /**
     * Build a template rendering context from the given client case and optional contract.
     */
    private function buildCaseEmailContext(ClientCase $clientCase, ?\App\Models\Contract $contract = null): array
    {
        $clientCase->loadMissing('client.person');
        $ctx = [
            'client_case' => $clientCase,
            'client' => $clientCase->client,
            'person' => optional($clientCase->client)->person,
            'mail_profile' => \App\Models\MailProfile::query()->where('active', true)->orderBy('priority')->orderBy('id')->first(),
        ];
        if ($contract) {
            $contract->loadMissing(['clientCase.client.person', 'account.type']);
            $ctx['contract'] = $contract;
        }

        return $ctx;
    }

    private function flattenMeta(array $meta, string $prefix = ''): array
    {
        $result = [];
        foreach ($meta as $key => $value) {
            $newKey = $prefix === '' ? $key : "{$prefix}.{$key}";

            if (is_array($value)) {
                // Check if it's a structured meta entry with 'value' field
                if (isset($value['value'])) {
                    $result[$newKey] = $value['value'];
                    // If parent key is numeric, also create direct alias without the number
                    if ($prefix !== '' && is_numeric($key)) {
                        $result[$key] = $value['value'];
                    }
                } else {
                    // Recursively flatten nested arrays
                    $nested = $this->flattenMeta($value, $newKey);
                    $result = array_merge($result, $nested);

                    // If current key is numeric, also flatten without it for easier access
                    if (is_numeric($key)) {
                        $directNested = $this->flattenMeta($value, $prefix);
                        foreach ($directNested as $dk => $dv) {
                            // Only add if not already set (prefer first occurrence)
                            if (! isset($result[$dk])) {
                                $result[$dk] = $dv;
                            }
                        }
                    }
                }
            } else {
                $result[$newKey] = $value;
            }
        }

        return $result;
    }
}
