<?php

namespace App\Http\Controllers;

use App\Models\Account;
use App\Models\Activity;
use App\Models\AuditLog;
use App\Models\Campaign;
use App\Models\Contact;
use App\Models\CrmCase;
use App\Models\CrmFile;
use App\Models\CrmFileAccessLog;
use App\Models\CrmFileSetting;
use App\Models\Deal;
use App\Models\FollowUp;
use App\Models\Lead;
use App\Models\Message;
use App\Models\Note;
use App\Models\Project;
use App\Models\Survey;
use App\Models\Ticket;
use App\Models\Todo;
use App\Models\TradeDocument;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Illuminate\View\View;
use App\Services\FileContentAnalysisService;
use App\Services\FileSecurityService;

class CrmFileController extends Controller
{
    private function modules(): array
    {
        return ['contacts'=>Contact::class,'leads'=>Lead::class,'deals'=>Deal::class,'accounts'=>Account::class,
            'tickets'=>Ticket::class,'campaigns'=>Campaign::class,'projects'=>Project::class,'cases'=>CrmCase::class,
            'trade-documents'=>TradeDocument::class,'activities'=>Activity::class,'notes'=>Note::class,
            'messages'=>Message::class,'surveys'=>Survey::class,'follow-ups'=>FollowUp::class,'todos'=>Todo::class];
    }

    public function index(Request $request, string $module, int $id): View
    {
        $this->useDefaultDateRange($request);
        $request->validate(['q'=>['nullable','string','max:100'],'date_from'=>['nullable','date'],'date_to'=>['nullable','date','after_or_equal:date_from']]);
        $search=trim((string)$request->input('q'));
        $record = $this->record($module, $id);
        $this->authorizeRecord($record, $module, 'view');
        $fileQuery = CrmFile::withTrashed()->with(['uploader', 'permissions.user'])
            ->where('fileable_type', $record::class)->where('fileable_id', $record->id)
            ->when($request->filled('date_from'), fn ($query) => $query->whereDate('created_at','>=',$request->date_from))
            ->when($request->filled('date_to'), fn ($query) => $query->whereDate('created_at','<=',$request->date_to))
            ->when($search!=='',fn($query)=>$query->where(fn($filter)=>$filter
                ->where('name','like',"%{$search}%")->orWhere('category','like',"%{$search}%")
                ->orWhere('description','like',"%{$search}%")->orWhere('original_name','like',"%{$search}%")));
        $user = auth()->user();
        if (!$user->is_admin && !$user->can('manage_crm_files') && (int) $this->recordOwnerId($record) !== (int) $user->id) {
            $fileQuery->visibleTo($user);
        }
        $files = $fileQuery->orderByDesc('is_current')->orderBy('name')->orderByDesc('version')->get();
        $relatedRecords = $this->availableRecords();
        $users = User::where('business_id', auth()->user()->business_id)->orderBy('name')->get();
        $fileSettings=$this->settings((int)auth()->user()->business_id);
        $storageUsed=(int)CrmFile::withTrashed()->where('business_id',auth()->user()->business_id)->sum('size');
        return view('crm-files.index', compact('module', 'record', 'files', 'relatedRecords', 'users','fileSettings','storageUsed'));
    }

    public function store(Request $request, string $module, int $id): RedirectResponse
    {
        $targetModule = $request->input('related_module', $module);
        $targetId = (int) $request->input('related_record_id', $id);
        $record = $this->record($targetModule, $targetId);
        $this->authorizeRecord($record, $targetModule, 'manage');
        $data = $this->validateUpload($request);
        $file = $this->createVersion($record, $targetModule, $request, $data);
        $this->audit($file, 'upload', true, ['related_module'=>$targetModule,'related_record_id'=>$targetId]);
        return back()->with('success', 'File uploaded and linked to '.str_replace('-', ' ', $targetModule).' #'.$targetId.'.');
    }

    public function replace(Request $request, CrmFile $file): RedirectResponse
    {
        $this->authorizeFile($file, 'replace');
        $data = $this->validateUpload($request, false);
        $record = $file->fileable;
        $module = array_search($record::class, $this->modules(), true);
        abort_unless($module, 422);

        $replacement = DB::transaction(function () use ($file, $record, $module, $request, $data) {
            CrmFile::where('root_file_id', $file->root_file_id ?: $file->id)->update(['is_current'=>false]);
            return $this->createVersion($record, $module, $request, $data, $file);
        });
        $this->audit($replacement, 'replace', true, ['replaced_file_id'=>$file->id]);
        return back()->with('success', "File replaced with version {$replacement->version}.");
    }

    public function compare(CrmFile $file, CrmFile $other): View
    {
        $this->authorizeFile($file, 'view'); $this->authorizeFile($other, 'view');
        abort_unless(($file->root_file_id ?: $file->id) === ($other->root_file_id ?: $other->id), 422, 'Only versions of the same file can be compared.');
        $comparison = [
            'size_difference' => $file->size - $other->size,
            'same_checksum' => filled($file->checksum) && $file->checksum === $other->checksum,
            'mime_changed' => $file->mime_type !== $other->mime_type,
            'text_diff' => null,
        ];
        $content=app(FileContentAnalysisService::class)->compare($file,$other);$comparison['text_diff']=$content['content_diff'];$comparison+=$content;
        $this->audit($file, 'compare', true, ['compared_with'=>$other->id]);
        return view('crm-files.compare', compact('file', 'other', 'comparison'));
    }

    public function grant(Request $request, CrmFile $file): RedirectResponse
    {
        $this->authorizeFile($file, 'grant');
        $data = $request->validate([
            'user_id' => [Rule::exists('users', 'id')->where(fn ($q) => $q->where('business_id', auth()->user()->business_id))],
            'can_view'=>'nullable|boolean','can_download'=>'nullable|boolean',
            'can_replace'=>'nullable|boolean','can_delete'=>'nullable|boolean',
        ]);
        $file->permissions()->updateOrCreate(['user_id'=>$data['user_id']], [
            'can_view'=>$request->boolean('can_view'), 'can_download'=>$request->boolean('can_download'),
            'can_replace'=>$request->boolean('can_replace'), 'can_delete'=>$request->boolean('can_delete'),
            'granted_by'=>auth()->id(),
        ]);
        $this->audit($file, 'permission_granted', true, ['grantee_user_id'=>$data['user_id']]);
        return back()->with('success', 'File permissions updated.');
    }

    public function view(CrmFile $file): BinaryFileResponse
    {
        $this->authorizeFile($file, 'view');
        $this->audit($file, 'view');
        return response()->file(Storage::disk('public')->path($file->path));
    }

    public function download(CrmFile $file)
    {
        $this->authorizeFile($file, 'download');
        $this->audit($file, 'download');
        return Storage::disk('public')->download($file->path, $file->original_name);
    }

    public function destroy(CrmFile $file): RedirectResponse
    {
        $this->authorizeFile($file, 'delete'); $file->delete(); $this->audit($file, 'delete');
        return back()->with('success', 'File moved to deleted items.');
    }

    public function restore(int $id): RedirectResponse
    {
        $file = CrmFile::onlyTrashed()->findOrFail($id); $this->authorizeFile($file, 'delete');
        $file->restore(); $this->audit($file, 'restore');
        return back()->with('success', 'File restored.');
    }

    public function forceDestroy(int $id): RedirectResponse
    {
        $file=CrmFile::withTrashed()->findOrFail($id);$this->authorizeFile($file,'delete');
        abort_unless(auth()->user()->is_admin || auth()->user()->can('manage_crm_files'),403);
        DB::transaction(function()use($file){$path=$file->path;$file->permissions()->delete();$file->accessLogs()->update(['crm_file_id'=>null]);$file->forceDelete();Storage::disk('public')->delete($path);});
        return back()->with('success','File version and stored content permanently removed.');
    }

    public function updateSettings(Request $request): RedirectResponse
    {
        abort_unless($request->user()->is_admin || $request->user()->can('manage_crm_files'),403);
        $data=$request->validate(['quota_mb'=>'required|integer|min:1|max:102400','duplicate_policy'=>['required',Rule::in(['reject','allow'])],'trash_retention_days'=>'required|integer|min:1|max:3650']);
        $this->settings((int)$request->user()->business_id)->update(['quota_bytes'=>$data['quota_mb']*1024*1024,'duplicate_policy'=>$data['duplicate_policy'],'trash_retention_days'=>$data['trash_retention_days']]);
        return back()->with('success','File storage policy updated.');
    }

    private function validateUpload(Request $request, bool $nameOptional = true): array
    {
        return $request->validate([
            'file'=>'required|file|max:20480|mimes:pdf,png,jpg,jpeg,webp,doc,docx,xls,xlsx,csv,txt',
            'name'=>($nameOptional ? 'nullable' : 'nullable').'|string|max:255',
            'category'=>'nullable|string|max:100','description'=>'nullable|string|max:2000',
            'access_level'=>'nullable|in:private,team,business',
            'related_module'=>['nullable', Rule::in(array_keys($this->modules()))],
            'related_record_id'=>'nullable|integer',
        ]);
    }

    private function createVersion(Model $record, string $module, Request $request, array $data, ?CrmFile $parent = null): CrmFile
    {
        $upload = $request->file('file');
        $scan=app(FileSecurityService::class)->scan($upload);abort_unless($scan['clean'],422,'Upload blocked by malware scan: '.$scan['details']);
        $checksum=hash('sha256',$upload->get());$this->enforceUploadPolicy((int)auth()->user()->business_id,(int)$upload->getSize(),$checksum);
        $name = $data['name'] ?? $parent?->name ?? pathinfo($upload->getClientOriginalName(), PATHINFO_FILENAME);
        $rootId = $parent?->root_file_id ?: $parent?->id;
        $version = $parent
            ? CrmFile::withTrashed()->where(fn($query)=>$query->where('root_file_id',$rootId)->orWhere('id',$rootId))->max('version') + 1
            : CrmFile::withTrashed()->where('fileable_type', $record::class)->where('fileable_id', $record->id)->where('name', $name)->max('version') + 1;
        $path = $upload->store('crm-files/'.auth()->user()->business_id.'/'.$module, 'public');
        $file = $record->morphMany(CrmFile::class, 'fileable')->create([
            'business_id'=>auth()->user()->business_id,'name'=>$name,
            'category'=>$data['category'] ?? $parent?->category,'description'=>$data['description'] ?? $parent?->description,
            'path'=>$path,'original_name'=>$upload->getClientOriginalName(),'mime_type'=>$upload->getMimeType(),
            'size'=>$upload->getSize(),'version'=>$version,'uploaded_by'=>auth()->id(),
            'parent_file_id'=>$parent?->id,'root_file_id'=>$rootId,'checksum'=>$checksum,
            'scan_status'=>$scan['status'],'scan_details'=>$scan['details'],'scanned_at'=>now(),
            'access_level'=>$data['access_level'] ?? $parent?->access_level ?? 'business','is_current'=>true,
        ]);
        if (!$file->root_file_id) $file->update(['root_file_id'=>$file->id]);
        if($parent && !$parent->root_file_id)$parent->update(['root_file_id'=>$parent->id]);
        if($parent)$parent->permissions()->get()->each(fn($permission)=>$file->permissions()->create($permission->only(['user_id','can_view','can_download','can_replace','can_delete','granted_by'])));
        $analysis=app(FileContentAnalysisService::class)->analyze($file);$file->update(['content_fingerprint'=>$analysis['fingerprint']]);
        return $file;
    }

    private function record(string $module, int $id): Model
    {
        abort_unless(isset($this->modules()[$module]), 404);
        $record = $this->modules()[$module]::findOrFail($id);
        abort_if(isset($record->business_id) && $record->business_id !== auth()->user()->business_id, 403);
        return $record;
    }

    private function authorizeRecord(Model $record, string $module, string $action): void
    {
        abort_unless($record->business_id === auth()->user()->business_id, 403);
        if ($action === 'view') return;
        $ownerId = $this->recordOwnerId($record);
        $permission = match ($module) {
            'contacts'=>'update_contacts','leads'=>'update_leads','deals'=>'update_deals','accounts'=>'update_accounts',
            'tickets'=>'update_tickets','campaigns'=>'update_campaigns', default=>'manage_crm_files',
        };
        abort_unless(auth()->user()->is_admin || (int)$ownerId === (int)auth()->id() || auth()->user()->can($permission), 403);
    }

    private function authorizeFile(CrmFile $file, string $action): void
    {
        $allowed = $file->business_id === auth()->user()->business_id && $this->canAccess($file, $action);
        if (!$allowed) $this->audit($file, $action, false);
        abort_unless($allowed, 403, 'You do not have permission to '.$action.' this file.');
        abort_unless(Storage::disk('public')->exists($file->path), 404);
    }

    private function canAccess(CrmFile $file, string $action): bool
    {
        $user = auth()->user();
        if ($user->is_admin || $user->can('manage_crm_files') || (int)$file->uploaded_by === (int)$user->id) return true;
        $grant = $file->permissions()->where('user_id', $user->id)->first();
        if ($grant) return match ($action) {
            'download'=>$grant->can_download, 'replace'=>$grant->can_replace, 'delete'=>$grant->can_delete,
            'grant'=>false, default=>$grant->can_view,
        };
        $recordOwner = $file->fileable ? $this->recordOwnerId($file->fileable) : null;
        if ((int)$recordOwner === (int)$user->id) return true;
        if ($file->access_level === 'business') {
            return in_array($action, ['view','download','compare'], true);
        }
        if ($file->access_level === 'team') {
            $uploader = $file->uploader;
            return in_array($action, ['view','download','compare'], true) &&
                ((int)$uploader?->id===(int)$user->id || (filled($user->team_name) && $uploader?->business_id===$user->business_id && $uploader?->team_name===$user->team_name));
        }
        return false;
    }

    private function recordOwnerId(Model $record): ?int
    {
        foreach (['owner_id','contact_owner_id','assigned_to','created_by','uploaded_by'] as $field) {
            if (isset($record->{$field}) && $record->{$field}) return (int)$record->{$field};
        }
        return null;
    }

    private function availableRecords(): array
    {
        $businessId = auth()->user()->business_id;
        return collect($this->modules())->mapWithKeys(function ($class, $module) use ($businessId) {
            $records = $class::where('business_id', $businessId)->latest()->limit(250)->get()
                ->mapWithKeys(fn ($record) => [$record->id => $record->name ?? $record->topic ?? $record->account_name ??
                    $record->campaign_name ?? $record->subject ?? $record->document_number ?? '#'.$record->id])->all();
            return [$module=>$records];
        })->all();
    }

    private function audit(CrmFile $file, string $action, bool $allowed = true, array $metadata = []): void
    {
        CrmFileAccessLog::create([
            'business_id'=>$file->business_id,'crm_file_id'=>$file->id,'user_id'=>auth()->id(),
            'action'=>$action,'allowed'=>$allowed,'ip_address'=>request()->ip(),'user_agent'=>request()->userAgent(),
            'metadata'=>$metadata,'occurred_at'=>now(),
        ]);
        $user = auth()->user();
        AuditLog::create([
            'business_id'=>$file->business_id,'user_id'=>$user?->id,'employee_name'=>$user?->name,
            'employee_email'=>$user?->email,'reporting_manager_id'=>$user?->reporting_manager_id,
            'reporting_manager_name'=>$user?->reportingManager?->name,'action'=>'file_'.$action,
            'entity_type'=>'CrmFile','entity_id'=>(string)$file->id,'route_name'=>request()->route()?->getName(),
            'method'=>request()->method(),'url'=>request()->fullUrl(),'response_status'=>$allowed?200:403,
            'request_data'=>$metadata,'ip_address'=>request()->ip(),'user_agent'=>request()->userAgent(),
            'performed_at'=>now(),
        ]);
    }
    private function settings(int $businessId): CrmFileSetting {return CrmFileSetting::firstOrCreate(['business_id'=>$businessId],['quota_bytes'=>1073741824,'duplicate_policy'=>'reject','trash_retention_days'=>30]);}
    private function enforceUploadPolicy(int $businessId,int $bytes,string $checksum): void {$settings=$this->settings($businessId);$used=(int)CrmFile::withTrashed()->where('business_id',$businessId)->sum('size');abort_if($used+$bytes>$settings->quota_bytes,422,'Business file-storage quota exceeded.');if($settings->duplicate_policy==='reject')abort_if(CrmFile::withTrashed()->where('business_id',$businessId)->where('checksum',$checksum)->exists(),422,'An identical file already exists for this business.');}
}
