<?php

namespace App\Http\Controllers;

use App\Models\Company;
use App\Models\Contact;
use App\Models\Deal;
use App\Models\DealStage;
use App\Models\Pipeline;
use App\Models\Task;
use App\Models\User;
use App\Models\WebForm;
use App\Support\CrmModuleManager;
use App\Support\FormStyleManager;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Throwable;

class PublicFormController extends Controller
{
    public function show(Request $request, WebForm $form): View
    {
        abort_unless($form->is_active, 404);

        $styleSettings = FormStyleManager::normalize($form->style_settings ?? []);

        return view('forms.public.show', [
            'form' => $form,
            'styleSettings' => $styleSettings,
            'styleCssVars' => FormStyleManager::cssVars($styleSettings),
            'styleFontUrl' => FormStyleManager::fontUrl($styleSettings),
            'styleCustomCss' => FormStyleManager::customCss($styleSettings),
            'isSubmissionLocked' => $this->submitResultAction($form) === 'hide_form'
                && $this->isSubmissionLocked($request, $form),
        ]);
    }

    public function submit(Request $request, WebForm $form, CrmModuleManager $moduleManager): RedirectResponse
    {
        abort_unless($form->is_active, 404);
        $submitResultAction = $this->submitResultAction($form);
        $publicFormRoute = route('public-forms.show', ['form' => $form->slug]);

        if ($submitResultAction === 'hide_form' && $this->isSubmissionLocked($request, $form)) {
            return redirect($publicFormRoute)->with('form_success', (string) $form->success_message);
        }

        $fields = collect($form->fields ?? []);
        $validationRules = $this->validationRulesFromFields($fields);
        $validated = $request->validate($validationRules);
        $payload = $this->normalizePayload($fields, $validated, $request);

        try {
            [$resultType, $resultId] = $this->executeBehavior($form, $fields, $payload, $moduleManager);

            $form->submissions()->create([
                'status' => 'success',
                'payload' => $payload,
                'result_type' => $resultType,
                'result_id' => $resultId,
                'ip_address' => $request->ip(),
                'user_agent' => Str::limit((string) $request->userAgent(), 1000, ''),
                'submitted_at' => now(),
            ]);
        } catch (ValidationException $exception) {
            throw $exception;
        } catch (Throwable) {
            $form->submissions()->create([
                'status' => 'error',
                'payload' => $payload,
                'ip_address' => $request->ip(),
                'user_agent' => Str::limit((string) $request->userAgent(), 1000, ''),
                'submitted_at' => now(),
            ]);

            return redirect($publicFormRoute)
                ->withInput()
                ->withErrors(['form' => 'Unable to process the form right now. Please try again later.']);
        }

        if ($submitResultAction === 'hide_form') {
            $this->lockSubmission($request, $form);

            return redirect($publicFormRoute)->with('form_success', (string) $form->success_message);
        }

        if ($submitResultAction === 'redirect') {
            $redirectUrl = $this->submitRedirectUrl($form);
            if ($redirectUrl !== null) {
                if (str_starts_with($redirectUrl, '/')) {
                    return redirect($redirectUrl);
                }

                return redirect()->away($redirectUrl);
            }
        }

        return redirect($publicFormRoute)->with('form_success', (string) $form->success_message);
    }

    private function submitResultAction(WebForm $form): string
    {
        $settings = $this->submitBehaviorSettings($form);
        $action = (string) ($settings['submit_result_action'] ?? 'reload');

        return in_array($action, ['reload', 'hide_form', 'redirect'], true) ? $action : 'reload';
    }

    private function submitRedirectUrl(WebForm $form): ?string
    {
        $settings = $this->submitBehaviorSettings($form);
        $url = trim((string) ($settings['redirect_url'] ?? ''));
        if ($url === '' || ! $this->isAllowedRedirectUrl($url)) {
            return null;
        }

        return $url;
    }

    /**
     * @return array<string, mixed>
     */
    private function submitBehaviorSettings(WebForm $form): array
    {
        return is_array($form->behavior_settings) ? $form->behavior_settings : [];
    }

    private function isAllowedRedirectUrl(string $url): bool
    {
        if (str_starts_with($url, '/')) {
            return true;
        }

        if (! preg_match('#^https?://#i', $url)) {
            return false;
        }

        return filter_var($url, FILTER_VALIDATE_URL) !== false;
    }

    private function isSubmissionLocked(Request $request, WebForm $form): bool
    {
        $lockMap = $request->session()->get('public_form_submission_locks', []);
        if (! is_array($lockMap)) {
            return false;
        }

        return in_array((int) $form->id, array_map(fn (mixed $value): int => (int) $value, $lockMap), true);
    }

    private function lockSubmission(Request $request, WebForm $form): void
    {
        $lockMap = $request->session()->get('public_form_submission_locks', []);
        if (! is_array($lockMap)) {
            $lockMap = [];
        }

        $lockMap[] = (int) $form->id;
        $lockMap = array_values(array_unique(array_map(fn (mixed $value): int => (int) $value, $lockMap)));
        $request->session()->put('public_form_submission_locks', $lockMap);
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $fields
     * @return array<string, mixed>
     */
    private function validationRulesFromFields(Collection $fields): array
    {
        $rules = [];

        foreach ($fields as $field) {
            $name = trim((string) ($field['name'] ?? ''));
            $type = (string) ($field['type'] ?? 'text');
            $required = (bool) ($field['required'] ?? false);

            if ($name === '') {
                continue;
            }

            $fieldRules = [];
            $fieldRules[] = $required ? 'required' : 'nullable';

            if ($type === 'email') {
                $fieldRules[] = 'email';
                $fieldRules[] = 'max:255';
            } elseif ($type === 'phone') {
                $fieldRules[] = 'string';
                $fieldRules[] = 'max:50';
            } elseif ($type === 'textarea') {
                $fieldRules[] = 'string';
                $fieldRules[] = 'max:5000';
            } elseif ($type === 'number') {
                $fieldRules[] = 'numeric';
            } elseif ($type === 'date' || $type === 'datetime') {
                $fieldRules[] = 'date';
            } elseif ($type === 'select') {
                $fieldRules[] = 'string';
                $fieldRules[] = 'max:255';

                $options = collect($field['options'] ?? [])->filter(fn ($value) => is_string($value) && trim($value) !== '')->values();
                if ($options->isNotEmpty()) {
                    $fieldRules[] = 'in:'.$options->implode(',');
                }
            } elseif ($type === 'checkbox') {
                $fieldRules[] = 'boolean';
            } else {
                $fieldRules[] = 'string';
                $fieldRules[] = 'max:1000';
            }

            $rules[$name] = $fieldRules;
        }

        return $rules;
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $fields
     * @param  array<string, mixed>  $validated
     * @return array<string, mixed>
     */
    private function normalizePayload(Collection $fields, array $validated, Request $request): array
    {
        $normalized = [];

        foreach ($fields as $field) {
            $name = trim((string) ($field['name'] ?? ''));
            $type = (string) ($field['type'] ?? 'text');

            if ($name === '') {
                continue;
            }

            if ($type === 'checkbox') {
                $normalized[$name] = $request->boolean($name);
                continue;
            }

            $normalized[$name] = $validated[$name] ?? null;
        }

        return $normalized;
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $fields
     * @param  array<string, mixed>  $payload
     * @return array{0:string, 1:int}
     */
    private function executeBehavior(WebForm $form, Collection $fields, array $payload, CrmModuleManager $moduleManager): array
    {
        $mapped = $this->mappedValues($fields, $payload);
        $mapped = $this->applyBehaviorFieldMap($form, $mapped, $payload);

        return match ((string) $form->behavior) {
            'create_company' => $this->createCompany($form, $mapped, $moduleManager),
            'create_contact' => $this->createContact($form, $mapped, $moduleManager),
            'create_deal' => $this->createDeal($form, $mapped, $moduleManager),
            default => $this->createTask($form, $mapped, $payload, $moduleManager),
        };
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $fields
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    private function mappedValues(Collection $fields, array $payload): array
    {
        $mapped = [];

        foreach ($fields as $field) {
            $name = trim((string) ($field['name'] ?? ''));
            $target = trim((string) ($field['target'] ?? ''));

            if ($name === '' || $target === '' || ! array_key_exists($name, $payload)) {
                continue;
            }

            $mapped[$target] = $payload[$name];
        }

        return $mapped;
    }

    /**
     * @param  array<string, mixed>  $mapped
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    private function applyBehaviorFieldMap(WebForm $form, array $mapped, array $payload): array
    {
        $settings = is_array($form->behavior_settings) ? $form->behavior_settings : [];
        $fieldMap = $settings['field_map'] ?? [];
        if (! is_array($fieldMap)) {
            return $mapped;
        }

        foreach ($fieldMap as $row) {
            if (! is_array($row)) {
                continue;
            }

            $crmField = trim((string) ($row['crm_field'] ?? ''));
            $formField = trim((string) ($row['form_field'] ?? ''));
            if ($crmField === '' || $formField === '') {
                continue;
            }

            if (! array_key_exists($formField, $payload)) {
                continue;
            }

            $mapped[$crmField] = $payload[$formField];
        }

        return $mapped;
    }

    /**
     * @param  array<string, mixed>  $mapped
     * @param  array<string, mixed>  $payload
     * @return array{0:string, 1:int}
     */
    private function createTask(WebForm $form, array $mapped, array $payload, CrmModuleManager $moduleManager): array
    {
        $settings = is_array($form->behavior_settings) ? $form->behavior_settings : [];
        $priority = $this->taskPriority($mapped['priority'] ?? ($settings['priority'] ?? null)) ?? 'medium';
        $status = $this->taskStatus($mapped['status'] ?? null) ?? 'todo';
        $assigneeId = $this->resolveUserId($mapped['assignee_id'] ?? ($settings['assignee_id'] ?? null))
            ?? $this->resolveUserId($form->owner_id);

        $description = trim((string) ($mapped['description'] ?? ''));
        if ($description === '') {
            $description = $this->payloadSummary($payload);
        }

        $taskPayload = [
            'title' => trim((string) ($mapped['title'] ?? $mapped['name'] ?? ('New task from form: '.$form->name))),
            'description' => $description,
            'deal_id' => $this->resolveEntityId($mapped['deal_id'] ?? null, Deal::class),
            'company_id' => $this->resolveEntityId($mapped['company_id'] ?? null, Company::class),
            'contact_id' => $this->resolveEntityId($mapped['contact_id'] ?? null, Contact::class),
            'creator_id' => $this->resolveUserId($form->owner_id),
            'assignee_id' => $assigneeId,
            'status' => $status,
            'priority' => $priority,
            'estimated_hours' => max(0, (float) ($mapped['estimated_hours'] ?? 0)),
            'tracked_hours' => 0,
            'sort_order' => 0,
            'starts_at' => $this->parseDateTime($mapped['starts_at'] ?? null),
            'due_at' => $this->parseDateTime($mapped['due_at'] ?? null),
        ];
        $taskPayload = $moduleManager->applyPayloadHooks('tasks.store', $taskPayload, $this->moduleHookContext($form, 'tasks.store'), array_keys($taskPayload));
        $taskPayload['completed_at'] = ($taskPayload['status'] ?? null) === 'done'
            ? ($taskPayload['completed_at'] ?? now())
            : null;

        $task = Task::query()->create($taskPayload);

        return ['task', (int) $task->id];
    }

    /**
     * @param  array<string, mixed>  $mapped
     * @return array{0:string, 1:int}
     */
    private function createCompany(WebForm $form, array $mapped, CrmModuleManager $moduleManager): array
    {
        $settings = is_array($form->behavior_settings) ? $form->behavior_settings : [];
        $status = $this->companyStatus($mapped['status'] ?? ($settings['status'] ?? null)) ?? 'lead';
        $ownerId = $this->resolveUserId($mapped['owner_id'] ?? ($settings['owner_id'] ?? null))
            ?? $this->resolveUserId($form->owner_id);

        $name = trim((string) ($mapped['name'] ?? $mapped['company_name'] ?? ''));
        if ($name === '') {
            $name = 'New company from form: '.$form->name;
        }

        $companyPayload = [
            'name' => $name,
            'industry' => $this->nullableString($mapped['industry'] ?? null),
            'website' => $this->nullableString($mapped['website'] ?? null),
            'phone' => $this->nullableString($mapped['phone'] ?? null),
            'email' => $this->nullableString($mapped['email'] ?? null),
            'address' => $this->nullableString($mapped['address'] ?? null),
            'owner_id' => $ownerId,
            'source' => $this->nullableString($mapped['source'] ?? 'web-form'),
            'status' => $status,
            'notes' => $this->nullableString($mapped['notes'] ?? null),
        ];
        $companyPayload = $moduleManager->applyPayloadHooks('companies.store', $companyPayload, $this->moduleHookContext($form, 'companies.store'), array_keys($companyPayload));

        $company = Company::query()->create($companyPayload);

        return ['company', (int) $company->id];
    }

    /**
     * @param  array<string, mixed>  $mapped
     * @return array{0:string, 1:int}
     */
    private function createContact(WebForm $form, array $mapped, CrmModuleManager $moduleManager): array
    {
        $settings = is_array($form->behavior_settings) ? $form->behavior_settings : [];

        $firstName = trim((string) ($mapped['first_name'] ?? $mapped['name'] ?? ''));
        if ($firstName === '') {
            $firstName = 'New';
        }

        $contactPayload = [
            'first_name' => $firstName,
            'last_name' => $this->nullableString($mapped['last_name'] ?? null),
            'title' => $this->nullableString($mapped['title'] ?? null),
            'email' => $this->nullableString($mapped['email'] ?? null),
            'phone' => $this->nullableString($mapped['phone'] ?? null),
            'company_id' => $this->resolveEntityId($mapped['company_id'] ?? null, Company::class),
            'owner_id' => $this->resolveUserId($mapped['owner_id'] ?? ($settings['owner_id'] ?? null)) ?? $this->resolveUserId($form->owner_id),
            'source' => $this->nullableString($mapped['source'] ?? 'web-form'),
            'notes' => $this->nullableString($mapped['notes'] ?? null),
        ];
        $contactPayload = $moduleManager->applyPayloadHooks('contacts.store', $contactPayload, $this->moduleHookContext($form, 'contacts.store'), array_keys($contactPayload));

        $contact = Contact::query()->create($contactPayload);

        return ['contact', (int) $contact->id];
    }

    /**
     * @param  array<string, mixed>  $mapped
     * @return array{0:string, 1:int}
     */
    private function createDeal(WebForm $form, array $mapped, CrmModuleManager $moduleManager): array
    {
        $settings = is_array($form->behavior_settings) ? $form->behavior_settings : [];
        $ownerId = $this->resolveUserId($mapped['owner_id'] ?? ($settings['owner_id'] ?? null))
            ?? $this->resolveUserId($form->owner_id);
        $pipelineId = $this->resolveEntityId($mapped['pipeline_id'] ?? null, Pipeline::class)
            ?? $this->resolveEntityId($settings['pipeline_id'] ?? null, Pipeline::class);
        $mappedStageId = $this->resolveEntityId($mapped['stage_id'] ?? null, DealStage::class);
        $settingsStageId = $this->resolveEntityId($settings['stage_id'] ?? null, DealStage::class);

        $pipeline = Pipeline::query()
            ->with(['stages' => fn ($query) => $query->orderBy('sort_order')])
            ->find($pipelineId);

        if (! $pipeline) {
            $pipeline = Pipeline::query()
                ->with(['stages' => fn ($query) => $query->orderBy('sort_order')])
                ->where('is_default', true)
                ->first()
                ?? Pipeline::query()->with(['stages' => fn ($query) => $query->orderBy('sort_order')])->orderBy('sort_order')->first();
        }

        if (! $pipeline) {
            throw ValidationException::withMessages([
                'form' => 'No sales funnel configured. Unable to create a deal from the form.',
            ]);
        }

        $stage = $pipeline->stages->firstWhere('id', (int) ($mappedStageId ?? 0))
            ?? $pipeline->stages->firstWhere('id', (int) ($settingsStageId ?? 0))
            ?? $pipeline->stages->first();

        if (! $stage) {
            throw ValidationException::withMessages([
                'form' => 'The selected funnel has no stages. Unable to create a deal from the form.',
            ]);
        }

        $companyId = $this->resolveEntityId($mapped['company_id'] ?? null, Company::class);
        if (! $companyId && trim((string) ($mapped['company_name'] ?? '')) !== '') {
            $companyPayload = [
                'name' => trim((string) $mapped['company_name']),
                'owner_id' => $ownerId,
                'status' => 'lead',
                'source' => 'web-form',
            ];
            $companyPayload = $moduleManager->applyPayloadHooks('companies.store', $companyPayload, $this->moduleHookContext($form, 'companies.store'), array_keys($companyPayload));

            $company = Company::query()->create($companyPayload);
            $companyId = (int) $company->id;
        }

        $contactId = $this->resolveEntityId($mapped['contact_id'] ?? null, Contact::class);
        if (! $contactId && (trim((string) ($mapped['first_name'] ?? '')) !== '' || trim((string) ($mapped['email'] ?? '')) !== '')) {
            $contactPayload = [
                'first_name' => trim((string) ($mapped['first_name'] ?? $mapped['name'] ?? 'New lead')),
                'last_name' => $this->nullableString($mapped['last_name'] ?? null),
                'email' => $this->nullableString($mapped['email'] ?? null),
                'phone' => $this->nullableString($mapped['phone'] ?? null),
                'company_id' => $companyId,
                'owner_id' => $ownerId,
                'source' => 'web-form',
            ];
            $contactPayload = $moduleManager->applyPayloadHooks('contacts.store', $contactPayload, $this->moduleHookContext($form, 'contacts.store'), array_keys($contactPayload));

            $contact = Contact::query()->create($contactPayload);
            $contactId = (int) $contact->id;
        }

        $currency = strtoupper((string) ($mapped['currency'] ?? ($settings['currency'] ?? 'USD')));
        if (strlen($currency) !== 3) {
            $currency = 'USD';
        }

        $priority = $this->dealPriority($mapped['priority'] ?? null) ?? 'medium';
        $status = $this->dealStatus($mapped['status'] ?? null) ?? 'open';
        $expectedCloseAt = $this->parseDate($mapped['expected_close_at'] ?? null);

        $dealPayload = [
            'title' => trim((string) ($mapped['title'] ?? $mapped['name'] ?? ('New lead from form: '.$form->name))),
            'pipeline_id' => $pipeline->id,
            'stage_id' => $stage->id,
            'company_id' => $companyId,
            'contact_id' => $contactId,
            'owner_id' => $ownerId,
            'amount' => max(0, (float) ($mapped['amount'] ?? 0)),
            'currency' => $currency,
            'priority' => $priority,
            'status' => $status,
            'expected_close_at' => $expectedCloseAt,
            'source' => trim((string) ($mapped['source'] ?? ('web-form:'.$form->slug))),
            'description' => $this->nullableString($mapped['description'] ?? null),
        ];
        $dealPayload = $moduleManager->applyPayloadHooks('deals.store', $dealPayload, $this->moduleHookContext($form, 'deals.store'), array_keys($dealPayload));
        $dealPayload['closed_at'] = ($dealPayload['status'] ?? 'open') === 'open'
            ? null
            : ($dealPayload['closed_at'] ?? now());

        $deal = Deal::query()->create($dealPayload);

        return ['deal', (int) $deal->id];
    }

    private function resolveUserId(mixed $value): ?int
    {
        $id = is_numeric($value) ? (int) $value : 0;
        if ($id <= 0) {
            return null;
        }

        return User::query()->whereKey($id)->exists() ? $id : null;
    }

    private function resolveEntityId(mixed $value, string $modelClass): ?int
    {
        $id = is_numeric($value) ? (int) $value : 0;
        if ($id <= 0) {
            return null;
        }

        return $modelClass::query()->whereKey($id)->exists() ? $id : null;
    }

    private function parseDateTime(mixed $value): ?Carbon
    {
        $raw = trim((string) ($value ?? ''));
        if ($raw === '') {
            return null;
        }

        try {
            return Carbon::parse($raw);
        } catch (Throwable) {
            return null;
        }
    }

    private function parseDate(mixed $value): ?Carbon
    {
        return $this->parseDateTime($value)?->startOfDay();
    }

    private function taskPriority(mixed $value): ?string
    {
        $normalized = strtolower(trim((string) ($value ?? '')));

        return in_array($normalized, ['low', 'medium', 'high', 'urgent'], true) ? $normalized : null;
    }

    private function taskStatus(mixed $value): ?string
    {
        $normalized = strtolower(trim((string) ($value ?? '')));

        return in_array($normalized, ['todo', 'in_progress', 'review', 'done'], true) ? $normalized : null;
    }

    private function companyStatus(mixed $value): ?string
    {
        $normalized = strtolower(trim((string) ($value ?? '')));

        return in_array($normalized, ['lead', 'client', 'partner', 'inactive'], true) ? $normalized : null;
    }

    private function dealPriority(mixed $value): ?string
    {
        $normalized = strtolower(trim((string) ($value ?? '')));

        return in_array($normalized, ['low', 'medium', 'high'], true) ? $normalized : null;
    }

    private function dealStatus(mixed $value): ?string
    {
        $normalized = strtolower(trim((string) ($value ?? '')));

        return in_array($normalized, ['open', 'won', 'lost'], true) ? $normalized : null;
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function payloadSummary(array $payload): string
    {
        $lines = [];

        foreach ($payload as $key => $value) {
            if (is_bool($value)) {
                $value = $value ? 'Yes' : 'No';
            }

            if ($value === null || (is_string($value) && trim($value) === '')) {
                continue;
            }

            $lines[] = Str::headline((string) $key).': '.(string) $value;
        }

        return implode(PHP_EOL, $lines);
    }

    private function nullableString(mixed $value): ?string
    {
        $string = trim((string) ($value ?? ''));

        return $string === '' ? null : $string;
    }

    /**
     * @return array<string, mixed>
     */
    private function moduleHookContext(WebForm $form, string $hook): array
    {
        return [
            'hook' => $hook,
            'source' => 'public_form',
            'form_id' => $form->id,
            'form_slug' => $form->slug,
            'behavior' => $form->behavior,
            'owner_id' => $form->owner_id,
        ];
    }
}
