<?php

namespace App\Http\Controllers;

use App\Models\CrmCase;
use App\Models\FollowUp;
use App\Models\Message;
use App\Models\Note;
use App\Models\Project;
use App\Models\Survey;
use App\Models\Todo;
use App\Models\TradeDocument;
use App\Models\EmailTemplate;
use App\Models\Account;
use App\Models\Campaign;
use App\Models\Contact;
use App\Models\Deal;
use App\Models\Lead;
use App\Models\Ticket;
use App\Models\Product;
use App\Models\DocumentTemplate;
use App\Models\DocumentNumberSetting;
use App\Models\User;
use App\Models\CrmAssignmentHistory;
use App\Models\IntegrationSyncLog;
use App\Models\AuditLog;
use App\Services\TradeDocumentService;
use App\Services\CrmNotificationService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use App\Services\CaseNumberService;
use Illuminate\Support\Facades\Schema;

class WorkflowModuleController extends Controller
{
    private function definitions(): array
    {
        return [
            'email-templates' => [
                'title' => 'Email Templates', 'model' => EmailTemplate::class,
                'fields' => [
                    'name' => ['label' => 'Template Name', 'rules' => 'required|max:255'],
                    'subject' => ['label' => 'Subject', 'rules' => 'required|max:255'],
                    'body' => ['label' => 'Email Body', 'type' => 'textarea', 'rules' => 'required'],
                    'category' => ['label' => 'Category', 'rules' => 'nullable|max:255'],
                    'active' => ['label' => 'Status', 'type' => 'select', 'options' => ['1', '0'], 'rules' => 'required|boolean'],
                ],
            ],
            'messages' => [
                'title' => 'Messages', 'model' => Message::class,
                'fields' => [
                    'type' => ['label' => 'Type', 'type' => 'select', 'options' => ['whatsapp', 'sms', 'chat', 'internal'], 'rules' => 'required'],
                    'subject' => ['label' => 'Subject', 'rules' => 'required|max:255'],
                    'from_address' => ['label' => 'From', 'rules' => 'required|max:255'],
                    'to_addresses' => ['label' => 'To', 'rules' => 'required'],
                    'content' => ['label' => 'Message', 'type' => 'textarea', 'rules' => 'required'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['sent', 'delivered', 'read', 'failed'], 'rules' => 'required'],
                    'conversation_id' => ['label' => 'Conversation ID', 'rules' => 'nullable|max:255'],
                    'owner_id' => ['label' => 'Owner ID', 'type' => 'number', 'rules' => 'required|integer|exists:users,id'],
                    'attachment' => ['label' => 'Attachment', 'type' => 'file', 'rules' => 'nullable|file|max:10240|mimes:pdf,png,jpg,jpeg,webp,doc,docx,xls,xlsx,csv,txt,mp3,mp4'],
                    'related_type' => ['label' => 'Related Module', 'type' => 'select', 'options' => ['contact','lead','deal','account','ticket','campaign','project','case','survey'], 'rules' => 'required'],
                    'related_id' => ['label' => 'Related Record ID', 'type' => 'number', 'rules' => 'required|integer'],
                ],
            ],
            'notes' => [
                'title' => 'Notes', 'model' => Note::class,
                'fields' => [
                    'owner_id' => ['label' => 'Owner ID', 'type' => 'number', 'rules' => 'required|integer'],
                    'title' => ['label' => 'Title', 'rules' => 'required|max:255'],
                    'description' => ['label' => 'Note', 'type' => 'textarea', 'rules' => 'required'],
                    'visibility' => ['label' => 'Visibility', 'type' => 'select', 'options' => ['private', 'team', 'public'], 'rules' => 'required'],
                    'attachment' => ['label' => 'Attachment', 'type' => 'file', 'rules' => 'nullable|file|max:10240|mimes:pdf,png,jpg,jpeg,webp,doc,docx,xls,xlsx,csv,txt'],
                    'related_type' => ['label' => 'Related Module', 'type' => 'select', 'options' => ['contact','lead','deal','account','ticket','campaign','project','case'], 'rules' => 'required'],
                    'related_id' => ['label' => 'Related Record ID', 'type' => 'number', 'rules' => 'required|integer'],
                ],
            ],
            'follow-ups' => [
                'title' => 'Follow-ups', 'model' => FollowUp::class,
                'fields' => [
                    'owner_id' => ['label' => 'Owner ID', 'type' => 'number', 'rules' => 'required|integer|exists:users,id'],
                    'title' => ['label' => 'Title', 'rules' => 'required|max:255'],
                    'follow_up_at' => ['label' => 'Follow-up Date & Time', 'type' => 'datetime-local', 'rules' => 'required|date'],
                    'priority' => ['label' => 'Priority', 'type' => 'select', 'options' => ['low', 'medium', 'high', 'urgent'], 'rules' => 'required'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['pending', 'completed', 'cancelled'], 'rules' => 'required'],
                    'remind_at' => ['label' => 'Reminder', 'type' => 'datetime-local', 'rules' => 'nullable|date'],
                    'reminder_escalation_hours' => ['label' => 'Escalate overdue reminder after', 'type' => 'select', 'options' => [4=>'4 hours',12=>'12 hours',24=>'24 hours',48=>'48 hours',72=>'72 hours'], 'rules' => 'nullable|integer|min:1|max:720'],
                    'notes' => ['label' => 'Notes', 'type' => 'textarea', 'rules' => 'nullable'],
                    'previous_activity_id' => ['label' => 'Previous Activity ID', 'type' => 'number', 'rules' => 'nullable|integer|exists:activities,id'],
                    'related_type' => ['label' => 'Related Module', 'type' => 'select', 'options' => ['contact','lead','deal','account','ticket','campaign','project','case','survey'], 'rules' => 'required'],
                    'related_id' => ['label' => 'Related Record ID', 'type' => 'number', 'rules' => 'required|integer'],
                ],
            ],
            'todos' => [
                'title' => 'To-dos', 'model' => Todo::class,
                'fields' => [
                    'owner_id' => ['label' => 'Owner ID', 'type' => 'number', 'rules' => 'nullable|integer|exists:users,id'],
                    'title' => ['label' => 'Title', 'rules' => 'required|max:255'],
                    'description' => ['label' => 'Description', 'type' => 'textarea', 'rules' => 'nullable'],
                    'comments' => ['label' => 'Comments', 'type' => 'textarea', 'rules' => 'nullable|max:5000'],
                    'start_at' => ['label' => 'Start Date & Time', 'type' => 'datetime-local', 'rules' => 'nullable|date'],
                    'priority' => ['label' => 'Priority', 'type' => 'select', 'options' => ['low', 'medium', 'high', 'urgent'], 'rules' => 'required'],
                    'due_at' => ['label' => 'Due Date & Time', 'type' => 'datetime-local', 'rules' => 'required|date'],
                    'remind_at' => ['label' => 'Reminder', 'type' => 'datetime-local', 'rules' => 'nullable|date'],
                    'reminder_escalation_hours' => ['label' => 'Escalate overdue reminder after', 'type' => 'select', 'options' => [4=>'4 hours',12=>'12 hours',24=>'24 hours',48=>'48 hours',72=>'72 hours'], 'rules' => 'nullable|integer|min:1|max:720'],
                    'category' => ['label' => 'Category', 'rules' => 'nullable|max:255'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['open', 'pending', 'in_progress', 'completed', 'cancelled'], 'rules' => 'required'],
                    'related_type' => ['label' => 'Related Module', 'type' => 'select', 'options' => ['contact','lead','deal','account','ticket','campaign','project','case','survey'], 'rules' => 'required'],
                    'related_id' => ['label' => 'Related Record ID', 'type' => 'number', 'rules' => 'required|integer'],
                ],
            ],
            'projects' => [
                'title' => 'Projects', 'model' => Project::class,
                'fields' => [
                    'name' => ['label' => 'Project Name', 'rules' => 'required|max:255'],
                    'description' => ['label' => 'Description', 'type' => 'textarea', 'rules' => 'nullable'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['planned', 'active', 'on_hold', 'completed', 'cancelled'], 'rules' => 'required'],
                    'start_date' => ['label' => 'Start Date', 'type' => 'date', 'rules' => 'nullable|date'],
                    'due_date' => ['label' => 'Due Date', 'type' => 'date', 'rules' => 'nullable|date|after_or_equal:start_date'],
                    'progress' => ['label' => 'Progress %', 'type' => 'number', 'rules' => 'required|integer|min:0|max:100'],
                ],
            ],
            'cases' => [
                'title' => 'Cases', 'model' => CrmCase::class,
                'fields' => [
                    'case_number' => ['label' => 'Case Number', 'rules' => 'nullable|max:255'],
                    'subject' => ['label' => 'Subject', 'rules' => 'required|max:255'],
                    'priority' => ['label' => 'Priority', 'type' => 'select', 'options' => ['low', 'medium', 'high', 'urgent'], 'rules' => 'required'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['new', 'in_progress', 'waiting', 'resolved', 'closed'], 'rules' => 'required'],
                    'category' => ['label' => 'Category', 'rules' => 'nullable|max:255'],
                    'due_at' => ['label' => 'Due Date', 'type' => 'datetime-local', 'rules' => 'nullable|date'],
                    'description' => ['label' => 'Description', 'type' => 'textarea', 'rules' => 'nullable'],
                    'resolution' => ['label' => 'Resolution', 'type' => 'textarea', 'rules' => 'nullable'],
                ],
            ],
            'surveys' => [
                'title' => 'Surveys', 'model' => Survey::class,
                'fields' => [
                    'name' => ['label' => 'Survey Name', 'rules' => 'required|max:255'],
                    'description' => ['label' => 'Description', 'type' => 'textarea', 'rules' => 'nullable'],
                    'type' => ['label' => 'Type', 'type' => 'select', 'options' => ['csat', 'nps', 'product_feedback', 'custom'], 'rules' => 'required'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['draft', 'active', 'closed'], 'rules' => 'required'],
                    'target_audience' => ['label' => 'Target Audience', 'type' => 'select', 'options' => ['leads', 'contacts', 'customers'], 'rules' => 'nullable'],
                    'start_date' => ['label' => 'Start Date', 'type' => 'date', 'rules' => 'nullable|date'],
                    'end_date' => ['label' => 'End Date', 'type' => 'date', 'rules' => 'nullable|date|after_or_equal:start_date'],
                ],
            ],
            'trade-documents' => [
                'title' => 'Trade Documents', 'model' => TradeDocument::class,
                'fields' => [
                    'deal_id' => ['label' => 'Deal ID', 'type' => 'number', 'rules' => 'required|integer'],
                    'account_id' => ['label' => 'Account ID', 'type' => 'number', 'rules' => 'nullable|integer'],
                    'contact_id' => ['label' => 'Contact ID', 'type' => 'number', 'rules' => 'nullable|integer'],
                    'salesperson_id' => ['label' => 'Salesperson ID', 'type' => 'number', 'rules' => 'nullable|integer'],
                    'type' => ['label' => 'Type', 'type' => 'select', 'options' => ['proposal', 'quotation', 'invoice'], 'rules' => 'required'],
                    'document_number' => ['label' => 'Document Number', 'rules' => 'nullable|max:255'],
                    'status' => ['label' => 'Status', 'type' => 'select', 'options' => ['draft', 'pending_approval', 'approved', 'sent', 'accepted', 'rejected', 'paid', 'cancelled'], 'rules' => 'required'],
                    'currency' => ['label' => 'Currency', 'rules' => 'required|max:8'],
                    'subtotal' => ['label' => 'Subtotal', 'type' => 'number', 'rules' => 'required|numeric|min:0'],
                    'discount' => ['label' => 'Discount', 'type' => 'number', 'rules' => 'required|numeric|min:0'],
                    'tax' => ['label' => 'Tax', 'type' => 'number', 'rules' => 'required|numeric|min:0'],
                    'issue_date' => ['label' => 'Issue Date', 'type' => 'date', 'rules' => 'required|date'],
                    'due_date' => ['label' => 'Due Date', 'type' => 'date', 'rules' => 'nullable|date|after_or_equal:issue_date'],
                    'valid_until' => ['label' => 'Valid Until', 'type' => 'date', 'rules' => 'nullable|date|after_or_equal:issue_date'],
                    'payment_terms' => ['label' => 'Payment Terms', 'rules' => 'nullable|max:255'],
                    'delivery_terms' => ['label' => 'Delivery Terms', 'rules' => 'nullable|max:255'],
                    'billing_address' => ['label' => 'Billing Address', 'type' => 'textarea', 'rules' => 'nullable'],
                    'delivery_address' => ['label' => 'Delivery Address', 'type' => 'textarea', 'rules' => 'nullable'],
                    'other_charges' => ['label' => 'Other Charges', 'type' => 'number', 'rules' => 'nullable|numeric|min:0'],
                    'notes' => ['label' => 'Notes', 'type' => 'textarea', 'rules' => 'nullable'],
                    'attachment_reference' => ['label' => 'Attachment Reference', 'rules' => 'nullable|max:255'],
                ],
            ],
        ];
    }

    private function module(string $module): array
    {
        abort_unless(isset($this->definitions()[$module]), 404);
        return $this->definitions()[$module];
    }

    public function index(Request $request, string $module): View|RedirectResponse
    {
        $this->authorizeModule($request, $module, 'view');
        if ($module === 'cases') return redirect()->route('cases.index');
        if ($module === 'surveys') return redirect()->route('surveys.index');
        $definition = $this->module($module);
        $model = $definition['model'];
        $modelTable = (new $model)->getTable();
        $filterableFields = collect($definition['fields'])->filter(fn ($field, $name) =>
            ($field['type'] ?? null) === 'select'
            && in_array($name, ['status', 'type', 'priority', 'visibility', 'related_type', 'target_audience'], true)
            && Schema::hasColumn($modelTable, $name)
        );
        $this->useDefaultDateRange($request);
        $filterRules = $filterableFields->mapWithKeys(fn ($field, $name) => [
            $name => ['nullable', Rule::in($field['options'])],
        ])->all();
        $request->validate(array_merge(['q'=>['nullable','string','max:100'],'conversation_search'=>['nullable','string','max:100'],'date_from'=>['nullable','date'],'date_to'=>['nullable','date','after_or_equal:date_from'],
            'active'=>['nullable', Rule::in(['1','0'])], 'category'=>['nullable','string','max:100'], 'related_id'=>['nullable','integer']], $filterRules));
        $search=trim((string)$request->input('q'));
        $searchColumns=collect(['name','title','subject','content','description','notes','document_number'])
            ->filter(fn($column)=>Schema::hasColumn((new $model)->getTable(),$column))->values();
        $recordQuery = $model::query()
            ->when($request->filled('date_from'), fn ($query) => $query->whereDate($modelTable.'.created_at','>=',$request->date_from))
            ->when($request->filled('date_to'), fn ($query) => $query->whereDate($modelTable.'.created_at','<=',$request->date_to))
            ->when($module === 'notes', fn ($query) => $query->visibleTo($request->user()))
            ->when($request->boolean('deleted'), fn ($query) => $query->onlyTrashed())
            ->when($module === 'email-templates' && $request->filled('active'), fn ($query) => $query->where('active', $request->active))
            ->when($module === 'email-templates' && $request->filled('category'), fn ($query) => $query->where('category', $request->category))
            ->when($search!==''&&$searchColumns->isNotEmpty(),fn($query)=>$query->where(function($filter)use($searchColumns,$search){
                foreach($searchColumns as $index=>$column){
                    $method=$index===0?'where':'orWhere';
                    $filter->{$method}($column,'like',"%{$search}%");
                }
            }));
        $relatedType = $request->input('related_type');
        $relatedId = $request->integer('related_id');
        if ($relatedType && $relatedId) {
            if ($module === 'notes') {
                $column = $relatedType.'_id';
                abort_unless(Schema::hasColumn($modelTable, $column), 422, 'Notes cannot be linked to this record type.');
                $recordQuery->where($column, $relatedId);
            } else {
                $relatedModel = $this->relatedModelMap()[$relatedType] ?? null;
                abort_unless($relatedModel, 422, 'Invalid related record type.');
                $recordQuery->where('related_type', $relatedModel)->where('related_id', $relatedId);
            }
        }
        foreach ($filterableFields as $name => $field) {
            if ($name === 'related_type' && $request->filled($name)) {
                if ($module === 'notes') {
                    $column = $request->input($name).'_id';
                    abort_unless(Schema::hasColumn($modelTable, $column), 422, 'Notes cannot be linked to this record type.');
                    $recordQuery->whereNotNull($column);
                } else {
                    $recordQuery->where('related_type', $this->relatedModelMap()[$request->input($name)] ?? '');
                }
                continue;
            }
            $recordQuery->when($request->filled($name), fn ($query) => $query->where($name, $request->input($name)));
        }
        $records = $recordQuery->latest()->paginate($module === 'email-templates' ? 25 : 20)->withQueryString();
        if (in_array($module, ['messages', 'notes', 'follow-ups', 'todos'], true)) {
            $records->load('assignments.user');
            $assignmentHistories = CrmAssignmentHistory::with(['user','actor'])
                ->where('assignable_type', $model)->whereIn('assignable_id', $records->pluck('id'))
                ->latest()->get()->groupBy('assignable_id');
        } else {
            $assignmentHistories = collect();
        }

        $products = collect();
        $templates = $module === 'trade-documents' ? DocumentTemplate::orderBy('name')->get() : collect();
        $numberSettings = $module === 'trade-documents' ? DocumentNumberSetting::get()->keyBy('type') : collect();
        if ($module === 'trade-documents') $records->load(['lines.product','approvalHistories.user','payments','deliveries']);
        $conversations = collect();
        $conversationMessages = collect();
        $selectedMessage = null;
        $providerHistory = collect();
        if ($module === 'messages') {
            $conversationSearch = trim((string) $request->input('conversation_search'));
            $conversations = Message::selectRaw("COALESCE(NULLIF(conversation_id,''), CONCAT('message-',id)) as thread_key, MAX(created_at) as latest_at, COUNT(*) as message_count")
                ->when($conversationSearch !== '', fn ($query) => $query->where(fn ($search) => $search
                    ->where('conversation_id', 'like', "%{$conversationSearch}%")
                    ->orWhere('subject', 'like', "%{$conversationSearch}%")
                    ->orWhere('to_addresses', 'like', "%{$conversationSearch}%")
                    ->orWhere('content', 'like', "%{$conversationSearch}%")))
                ->groupBy('thread_key')->orderByDesc('latest_at')
                ->simplePaginate(30, ['*'], 'conversation_page')->withQueryString();
            $selectedConversation = $request->input('conversation', $conversations->first()?->thread_key);
            $conversationMessages = Message::with(['assignments.user','files'])
                ->where(fn ($query) => str_starts_with((string)$selectedConversation, 'message-')
                    ? $query->whereKey((int)str_replace('message-', '', $selectedConversation))
                    : $query->where('conversation_id', $selectedConversation))
                ->oldest()->get();
            $selectedMessage = $conversationMessages->firstWhere('id', (int)$request->input('message_id'))
                ?: $conversationMessages->last();
            $providerHistory = IntegrationSyncLog::whereIn('provider',['whatsapp','facebook','mailchimp'])
                ->latest()->limit(300)->get()->filter(fn ($log) =>
                    $conversationMessages->contains(fn ($message) =>
                        str_contains(json_encode($log->payload), (string)$message->conversation_id)
                        || str_contains(json_encode($log->payload), (string)$message->id)
                    )
                )->values();
        }
        $users = User::where('business_id', $request->user()->business_id)->orderBy('name')->get();
        $relatedRecords = collect();
        $view = $module === 'email-templates' ? 'workflow-modules.email-templates' : 'workflow-modules.index';
        return view($view, compact('module', 'definition', 'records', 'products', 'templates', 'numberSettings', 'users', 'relatedRecords', 'assignmentHistories',
            'conversations','conversationMessages','selectedMessage','providerHistory'));
    }

    public function relatedRecords(Request $request): JsonResponse
    {
        $request->validate([
            'type' => ['required', Rule::in(['contact','customer','lead','deal','won_deal','account','ticket','campaign','project','case','survey','product','user','activity'])],
            'q' => ['nullable', 'string', 'max:100'],
            'selected_id' => ['nullable', 'integer'],
            'owner_id' => ['nullable', 'integer', Rule::exists('users', 'id')->where(
                fn ($query) => $query->where('business_id', $request->user()->business_id)
            )],
        ]);

        $definitions = [
            'contact' => [Contact::class, ['first_name','last_name']],
            'customer' => [Contact::class, ['first_name','last_name']],
            'lead' => [Lead::class, ['first_name','last_name','topic']],
            'deal' => [Deal::class, ['name']],
            'won_deal' => [Deal::class, ['name']],
            'account' => [Account::class, ['account_name']],
            'ticket' => [Ticket::class, ['topic']],
            'campaign' => [Campaign::class, ['campaign_name']],
            'project' => [Project::class, ['name']],
            'case' => [CrmCase::class, ['subject','case_number']],
            'survey' => [Survey::class, ['name']],
            'product' => [Product::class, ['product_name','product_code']],
            'user' => [User::class, ['name','email']],
            'activity' => [\App\Models\Activity::class, ['title','subject']],
        ];
        [$model, $columns] = $definitions[$request->string('type')->toString()];
        $term = trim((string) $request->input('q'));
        $selectedId = $request->integer('selected_id');
        $records = $model::query()
            ->where('business_id', $request->user()->business_id)
            ->when(in_array($request->string('type')->toString(), ['contact', 'customer'], true) && $request->filled('owner_id'),
                fn ($query) => $query->where('contact_owner_id', $request->integer('owner_id')))
            ->when($request->string('type')->toString()==='customer',fn($query)=>$query->whereRaw('LOWER(type) = ?', ['customer']))
            ->when($request->string('type')->toString()==='won_deal',fn($query)=>$query->whereHas('stage',fn($stage)=>$stage->where('name','Closed Won')))
            ->when($selectedId, fn ($query) => $query->whereKey($selectedId))
            ->when(!$selectedId && $term !== '', fn ($query) => $query->where(function ($search) use ($columns, $term) {
                foreach ($columns as $index => $column) {
                    $method = $index === 0 ? 'where' : 'orWhere';
                    $search->{$method}($column, 'like', "%{$term}%");
                }
                if (is_numeric($term)) {
                    $search->orWhere($search->getModel()->getKeyName(), (int) $term);
                }
            }))
            ->orderBy($columns[0])
            ->limit(30)
            ->get();

        if ($records->first() instanceof Contact) {
            $records->load('assignedUser:id,name');
        }

        return response()->json($records->map(function ($record) use ($columns) {
            $label = trim(collect($columns)->map(fn ($column) => $record->{$column})->filter()->join(' '));
            return ['id' => $record->id, 'text' => $label ?: class_basename($record).' #'.$record->id,
                'owner_id' => $record instanceof Contact ? $record->contact_owner_id : null,
                'owner_name' => $record instanceof Contact ? ($record->assignedUser?->name ?? 'Unassigned') : null,
                'price' => $record instanceof Product ? (float) $record->price : null];
        })->values());
    }

    public function store(Request $request, string $module): RedirectResponse
    {
        $this->authorizeModule($request, $module, 'create');
        if ($module === 'messages') {
            $request->validate([
                'owner_id' => ['required','integer',Rule::exists('users','id')->where(fn($query)=>$query->where('business_id',$request->user()->business_id))],
                'assignee_ids' => 'required|array|min:1',
                'assignee_ids.*' => ['integer',Rule::exists('users','id')->where(fn($query)=>$query->where('business_id',$request->user()->business_id))],
                'platform' => ['required',Rule::in(['whatsapp','sms','facebook','instagram','linkedin','internal'])],
            ]);
        }
        if (in_array($module, ['follow-ups','todos'], true)) {
            $request->validate(['assignee_ids'=>'required|array|min:1','assignee_ids.*'=>['integer', Rule::exists('users','id')->where(fn($q)=>$q->where('business_id',$request->user()->business_id))]]);
        }
        if ($module === 'follow-ups') {
            $request->validate(['owner_id'=>['required','integer',Rule::exists('users','id')->where(fn($query)=>$query->where('business_id',$request->user()->business_id))]]);
        }
        if ($module === 'todos' && $request->filled('owner_id')) {
            $request->validate(['owner_id'=>['integer',Rule::exists('users','id')->where(fn($query)=>$query->where('business_id',$request->user()->business_id))]]);
        }
        $definition = $this->module($module);
        $rules = collect($definition['fields'])->mapWithKeys(fn ($field, $name) => [$name => $field['rules']])->all();
        $data = $request->validate($rules);
        if (in_array($module, ['follow-ups', 'todos'], true) && empty($data['reminder_escalation_hours'])) {
            $column = $module === 'follow-ups' ? 'follow_up_hours' : 'todo_hours';
            $data['reminder_escalation_hours'] = \DB::table('reminder_escalation_settings')->where('business_id', $request->user()->business_id)->value($column) ?: 24;
        }
        if ($module === 'follow-ups' && !empty($data['previous_activity_id'])) abort_unless(\App\Models\Activity::whereKey($data['previous_activity_id'])->where('business_id',$request->user()->business_id)->exists(),422,'Select an activity in the current business.');
        $attachment = in_array($module, ['messages', 'notes', 'follow-ups', 'todos'], true) ? $request->file('attachment') : null;
        unset($data['attachment']);
        if ($module === 'notes') abort_unless(User::whereKey($data['owner_id'])->where('business_id',$request->user()->business_id)->exists(), 422, 'Select a note owner in the current business.');
        if ($module === 'trade-documents') { $this->validateTradeDocumentRelations($data, $request); $this->validateQuotationRequirements($data, $request); }
        $data = $this->normalizeRelated($data, $module);
        $data['business_id'] = $request->user()->business_id;
        if ($module === 'messages') {
            $data['provider_metadata'] = ['platform' => $request->platform];
            $timestamp = now();
            if (in_array($data['status'], ['sent','delivered','read'], true)) $data['sent_at'] = $timestamp;
            if (in_array($data['status'], ['delivered','read'], true)) $data['delivered_at'] = $timestamp;
            if ($data['status'] === 'read') $data['read_at'] = $timestamp;
            if ($data['status'] === 'failed') $data['failed_at'] = $timestamp;
        }
        if ($module === 'cases' && empty($data['case_number'])) {
            $data['case_number'] = app(CaseNumberService::class)->next((int) $request->user()->business_id);
        }
        if ($module === 'trade-documents') {
            $data['created_by'] = $request->user()->id;
            $data['document_number'] = app(TradeDocumentService::class)->nextNumber($request->user()->business_id,$data['type']);
            $data['template_id'] = $request->input('template_id') ?: DocumentTemplate::where(fn($q)=>$q->where('type',$data['type'])->orWhere('type','all'))->where('is_default',true)->value('id');
            $data['customer_email'] = $request->input('customer_email');
            $data['total'] = max(0, (float) $data['subtotal'] - (float) $data['discount'] + (float) $data['tax'] + (float) ($data['other_charges'] ?? 0));
        }
        if ($module === 'todos') {
            $data['created_by'] = $request->user()->id;
            $data['owner_id'] = $data['owner_id'] ?? $request->user()->id;
        }
        if (in_array($module, ['messages', 'follow-ups', 'projects', 'surveys'], true) && empty($data['owner_id'])) {
            $data['owner_id'] = $request->user()->id;
        }
        if ($module === 'email-templates') $data['owner_id'] = $request->user()->id;
        $record = $definition['model']::create($data);
        if ($attachment) $this->storeWorkflowAttachment($record, $attachment, $module, $request);
        $this->syncAssignments($request, $record, $module);
        return back()->with('success', $definition['title'].' record created.');
    }

    private function storeWorkflowAttachment(Model $record, $attachment, string $module, Request $request): void
    {
        $path = $attachment->store('crm-files/'.$request->user()->business_id.'/'.$module, 'public');
        $record->files()->create(['business_id' => $request->user()->business_id, 'name' => pathinfo($attachment->getClientOriginalName(), PATHINFO_FILENAME), 'category' => $module, 'path' => $path, 'original_name' => $attachment->getClientOriginalName(), 'mime_type' => $attachment->getMimeType(), 'size' => $attachment->getSize(), 'uploaded_by' => $request->user()->id]);
    }

    private function validateQuotationRequirements(array $data, Request $request): void
    {
        if (($data['type'] ?? null) !== 'quotation') return;
        $missing = collect(['account_id','contact_id','salesperson_id','valid_until','payment_terms','delivery_terms','billing_address','delivery_address','attachment_reference'])
            ->filter(fn ($field) => blank($data[$field] ?? null))
            ->mapWithKeys(fn ($field) => [$field => 'This field is required for a quotation.'])->all();
        if ($missing) throw ValidationException::withMessages($missing);
        foreach (['account_id' => 'accounts', 'contact_id' => 'contacts', 'salesperson_id' => 'users'] as $field => $table) {
            if (! \DB::table($table)->where('id', $data[$field])->where('business_id', $request->user()->business_id)->exists()) {
                throw ValidationException::withMessages([$field => 'Select a record in the current business.']);
            }
        }
    }

    private function validateTradeDocumentRelations(array $data, Request $request): void
    {
        foreach (['deal_id' => 'deals', 'account_id' => 'accounts', 'contact_id' => 'contacts', 'salesperson_id' => 'users'] as $field => $table) {
            if (filled($data[$field] ?? null) && ! \DB::table($table)->where('id', $data[$field])->where('business_id', $request->user()->business_id)->exists()) {
                throw ValidationException::withMessages([$field => 'Select a record in the current business.']);
            }
        }
    }

    public function update(Request $request, string $module, int $id): RedirectResponse
    {
        $this->authorizeModule($request, $module, 'update');
        $definition = $this->module($module);
        $model = $definition['model'];
        $record = $model::where('business_id', $request->user()->business_id)->findOrFail($id);
        $previousOwnerId = $module === 'notes' ? $record->owner_id : null;
        $rules = collect($definition['fields'])->mapWithKeys(fn ($field, $name) => [$name => $field['rules']])->all();
        $data = $request->validate($rules);
        if ($module === 'notes') abort_unless(User::whereKey($data['owner_id'])->where('business_id',$request->user()->business_id)->exists(), 422, 'Select a note owner in the current business.');
        if ($module === 'trade-documents') { $this->validateTradeDocumentRelations($data, $request); $this->validateQuotationRequirements($data, $request); }
        $data = $this->normalizeRelated($data, $module);
        if ($module === 'trade-documents') {
            $data['total'] = max(0, (float) $data['subtotal'] - (float) $data['discount'] + (float) $data['tax'] + (float) ($data['other_charges'] ?? 0));
        }
        $record->update($data);
        if ($module === 'notes' && (int) $previousOwnerId !== (int) $record->owner_id) AuditLog::create(['business_id'=>$request->user()->business_id,'user_id'=>$request->user()->id,'action'=>'note_owner_changed','entity_type'=>Note::class,'entity_id'=>$record->id,'old_values'=>['owner_id'=>$previousOwnerId],'new_values'=>['owner_id'=>$record->owner_id],'performed_at'=>now()]);
        $this->syncAssignments($request, $record, $module);
        return back()->with('success', $definition['title'].' record updated.');
    }

    public function updateTodoComments(Request $request, Todo $todo): RedirectResponse
    {
        $this->authorizeModule($request, 'todos', 'update');
        abort_unless($todo->business_id === $request->user()->business_id, 403);

        $todo->update($request->validate(['comments' => 'nullable|string|max:5000']));

        return back()->with('success', 'To-do comments updated.');
    }

    public function destroy(string $module, int $id): RedirectResponse
    {
        $this->authorizeModule(request(), $module, 'delete');
        $definition = $this->module($module);
        $definition['model']::where('business_id', request()->user()->business_id)->findOrFail($id)->delete();
        return back()->with('success', 'Record moved to deleted items.');
    }

    public function restore(string $module, int $id): RedirectResponse
    {
        $this->authorizeModule(request(), $module, 'update');
        $definition = $this->module($module);
        $definition['model']::onlyTrashed()->where('business_id', request()->user()->business_id)->findOrFail($id)->restore();
        return back()->with('success', 'Record restored.');
    }

    private function normalizeRelated(array $data, string $module): array
    {
        if ($module === 'notes') {
            foreach (['contact_id','lead_id','deal_id','account_id','ticket_id','campaign_id'] as $column) {
                $data[$column] = null;
            }
            if (empty($data['related_type'])) {
                unset($data['related_type'], $data['related_id']);
                return $data;
            }
        } elseif (empty($data['related_type'])) {
            return $data;
        }
        $map = $this->relatedModelMap();
        abort_unless(isset($map[$data['related_type']]), 422);
        $model = $map[$data['related_type']];
        $record = $model::findOrFail($data['related_id']);
        abort_if(isset($record->business_id) && $record->business_id !== auth()->user()->business_id, 403);
        if ($module === 'notes') {
            $column = $data['related_type'].'_id';
            unset($data['related_type'], $data['related_id']);
            $data[$column] = $record->id;
            return $data;
        }
        $data['related_type'] = $model;
        return $data;
    }

    private function relatedModelMap(): array
    {
        return ['contact'=>Contact::class,'lead'=>Lead::class,'deal'=>Deal::class,'account'=>Account::class,
            'ticket'=>Ticket::class,'campaign'=>Campaign::class,'project'=>Project::class,'case'=>CrmCase::class,
            'survey'=>Survey::class];
    }

    private function syncAssignments(Request $request, Model $record, string $module): void
    {
        if (!in_array($module, ['messages', 'notes', 'follow-ups', 'todos'], true)) return;
        $ids = User::where('business_id', $request->user()->business_id)
            ->whereIn('id', (array) $request->input('assignee_ids', []))->pluck('id');
        $existing = $record->assignments()->pluck('user_id');
        foreach ($existing->diff($ids) as $id) {
            CrmAssignmentHistory::create(['business_id'=>$request->user()->business_id,
                'assignable_type'=>$record::class,'assignable_id'=>$record->id,'user_id'=>$id,
                'changed_by'=>$request->user()->id,'action'=>'unassigned']);
        }
        foreach ($ids->diff($existing) as $id) {
            CrmAssignmentHistory::create(['business_id'=>$request->user()->business_id,
                'assignable_type'=>$record::class,'assignable_id'=>$record->id,'user_id'=>$id,
                'changed_by'=>$request->user()->id,'action'=>'assigned']);
            $assignee = User::find($id);
            if ($assignee && $assignee->id !== $request->user()->id) {
                app(CrmNotificationService::class)->assignment(
                    $assignee,
                    $record,
                    $this->module($module)['title'],
                    route('workflow.index', ['module' => $module])
                );
            }
        }
        $record->assignments()->whereNotIn('user_id', $ids)->delete();
        foreach ($ids as $id) {
            $record->assignments()->firstOrCreate(['user_id' => $id], [
                'business_id' => $request->user()->business_id,
                'assigned_by' => $request->user()->id,
            ]);
        }
    }

    private function authorizeModule(Request $request, string $module, string $action): void
    {
        $this->module($module);
        $permission = $action.'_'.str_replace('-', '_', $module);
        abort_unless((int)$request->user()->is_admin === 1 || $request->user()->can($permission), 403);
    }
}
