<?php
namespace App\Http\Controllers;

use App\Models\Account;
use App\Models\Contact;
use App\Models\ContactEmail;
use App\Models\Deal;
use App\Models\Lead;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use App\Models\SurveyRecipient;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class SurveyController extends Controller
{
    public function __construct()
    {
        $this->middleware(function($request,$next){
            if(!$request->user())return $next($request);
            $method=$request->route()?->getActionMethod();
            if(in_array($method,['responseForm','submit','thanks'],true))return $next($request);
            $permission=match($method){'store'=>'create_surveys','index','show'=>'view_surveys',default=>'update_surveys'};
            abort_unless($request->user()->is_admin || $request->user()->can($permission),403);
            return $next($request);
        });
    }

    private function map(): array {return ['contacts'=>Contact::class,'leads'=>Lead::class,'accounts'=>Account::class,'deals'=>Deal::class];}

    public function index(Request $request): View {
        $this->useDefaultDateRange($request);
        $request->validate([
            'q'=>['nullable','string','max:100'],'status'=>['nullable',Rule::in(['draft','active','closed'])],
            'type'=>['nullable','string','max:100'],'date_from'=>['nullable','date'],
            'date_to'=>['nullable','date','after_or_equal:date_from'],
        ]);
        $search=trim((string)$request->input('q'));
        $surveys=Survey::with('owner')->withCount(['questionRecords','responses','recipients'])
            ->when($request->filled('status'),fn($q)=>$q->where('status',$request->status))
            ->when($request->filled('type'),fn($q)=>$q->where('type',$request->type))
            ->when($request->filled('date_from'),fn($q)=>$q->whereDate('created_at','>=',$request->date_from))
            ->when($request->filled('date_to'),fn($q)=>$q->whereDate('created_at','<=',$request->date_to))
            ->when($search!=='',fn($query)=>$query->where(fn($filter)=>$filter
                ->where('name','like',"%{$search}%")
                ->orWhere('description','like',"%{$search}%")
                ->orWhere('target_audience','like',"%{$search}%")))
            ->latest()->paginate(20)->withQueryString();
        return view('surveys.index',compact('surveys'));
    }
    public function store(Request $request): RedirectResponse {
        $data=$this->validatedSurvey($request);$data['business_id']=$request->user()->business_id;$data['owner_id']=$request->user()->id;
        $survey=Survey::create($data);return redirect()->route('surveys.show',$survey)->with('success','Survey created. Add questions and distribute it.');
    }
    public function show(Request $request,Survey $survey): View {
        $this->authorizeBusiness($survey);
        $survey->load(['questionRecords','recipients.respondent','responses.respondent','owner']);
        $analytics=$this->analytics($survey);
        return view('surveys.show',compact('survey','analytics'));
    }
    public function update(Request $request,Survey $survey): RedirectResponse {
        $this->authorizeBusiness($survey);$survey->update($this->validatedSurvey($request));return back()->with('success','Survey updated.');
    }
    public function questionStore(Request $request,Survey $survey): RedirectResponse {
        $this->authorizeBusiness($survey);$data=$this->validatedQuestion($request);
        $data['position']=$data['position']??($survey->questionRecords()->max('position')+1);$survey->questionRecords()->create($data);return back()->with('success','Question added.');
    }
    public function questionUpdate(Request $request,Survey $survey,SurveyQuestion $question): RedirectResponse {
        $this->authorizeChild($survey,$question);$question->update($this->validatedQuestion($request));return back()->with('success','Question updated.');
    }
    public function questionDestroy(Survey $survey,SurveyQuestion $question): RedirectResponse {
        $this->authorizeChild($survey,$question);abort_if($survey->responses()->exists(),422,'Questions cannot be deleted after responses exist.');$question->delete();return back()->with('success','Question removed.');
    }
    public function distribute(Request $request,Survey $survey): RedirectResponse {
        $this->authorizeBusiness($survey);abort_if($survey->questionRecords()->doesntExist(),422,'Add at least one question before distribution.');
        $data=$request->validate(['module'=>['required',Rule::in(array_keys($this->map()))],'record_ids'=>'required|array|min:1','record_ids.*'=>'integer','send_email'=>'nullable|boolean']);
        $class=$this->map()[$data['module']];$records=$class::where('business_id',$survey->business_id)->whereIn('id',$data['record_ids'])->get();
        abort_if($records->count()!==count(array_unique($data['record_ids'])),422,'One or more selected CRM records are invalid.');
        foreach($records as $record){
            $email=$this->emailFor($record);$recipient=$survey->recipients()->firstOrCreate(
                ['respondent_type'=>$class,'respondent_id'=>$record->id],
                ['token'=>(string)Str::uuid(),'email'=>$email,'status'=>'pending']
            );
            $this->attachProfile($survey,$data['module'],$record->id);
            if($request->boolean('send_email') && $email)$this->deliver($recipient,false);
        }
        $survey->update(['status'=>'active']);return back()->with('success',$records->count().' CRM respondent(s) added to the Survey.');
    }
    public function resend(Survey $survey,SurveyRecipient $recipient): RedirectResponse {
        $this->authorizeRecipient($survey,$recipient);abort_if(!$recipient->email,422,'This recipient has no email address.');
        $this->deliver($recipient,false);return back()->with('success','Survey invitation delivery was attempted again.');
    }
    public function remind(Survey $survey,SurveyRecipient $recipient): RedirectResponse {
        $this->authorizeRecipient($survey,$recipient);abort_if($recipient->status==='responded',422,'A completed respondent cannot be reminded.');abort_if(!$recipient->email,422,'This recipient has no email address.');
        $this->deliver($recipient,true);return back()->with('success','Survey reminder delivery was attempted.');
    }
    public function responseForm(string $token): View {
        [$survey,$recipient]=$this->resolvePublicToken($token);$this->assertAvailable($survey);
        if($recipient && !$recipient->opened_at)$recipient->update(['opened_at'=>now(),'status'=>'opened']);
        return view('surveys.respond',compact('survey','recipient'));
    }
    public function submit(Request $request,string $token): RedirectResponse {
        [$survey,$recipient]=$this->resolvePublicToken($token);$this->assertAvailable($survey);
        if($recipient)abort_if($recipient->response()->exists(),422,'This Survey has already been submitted.');
        $rules=[];foreach($survey->questionRecords as $q){
            $base=$q->required?'required':'nullable';
            $rules['answers.'.$q->id]=match($q->answer_type){
                'text'=>$base.'|string|max:500','textarea'=>$base.'|string|max:5000',
                'single_choice'=>[$base,Rule::in($q->options??[])],
                'multiple_choice'=>$base.'|array'.($q->required?'|min:1':''),
                'rating'=>$base.'|integer|min:1|max:5','nps'=>$base.'|integer|min:0|max:10',
                'yes_no'=>$base.'|in:Yes,No',default=>$base,
            };
            if($q->answer_type==='multiple_choice')$rules['answers.'.$q->id.'.*']=['string',Rule::in($q->options??[])];
        }
        $answers=$request->validate($rules)['answers']??[];
        DB::transaction(function()use($survey,$recipient,$answers,$request){
            if($recipient){$locked=SurveyRecipient::whereKey($recipient->id)->lockForUpdate()->firstOrFail();abort_if($locked->response()->exists(),422,'This Survey has already been submitted.');}
            $survey->responses()->create(['survey_recipient_id'=>$recipient?->id,'respondent_type'=>$recipient?->respondent_type,'respondent_id'=>$recipient?->respondent_id,'answers'=>$answers,'submitted_at'=>now(),'ip_address'=>$recipient?$request->ip():null]);
            if($recipient)$recipient->update(['status'=>'responded','responded_at'=>now()]);
        });
        return redirect()->route('surveys.thanks',$token);
    }
    public function thanks(string $token): View {[$survey,$recipient]=$this->resolvePublicToken($token);return view('surveys.thanks',compact('recipient','survey'));}

    private function validatedSurvey(Request $request): array{$data=$request->validate(['name'=>'required|max:255','description'=>'nullable|string','type'=>'required|in:csat,nps,product_feedback,custom','status'=>'required|in:draft,active,closed','target_audience'=>'nullable|max:255','allow_anonymous'=>'nullable|boolean','start_date'=>'nullable|date','end_date'=>'nullable|date|after_or_equal:start_date']);$data['allow_anonymous']=$request->boolean('allow_anonymous');if($data['allow_anonymous'])$data['anonymous_token']=$request->route('survey')?->anonymous_token?:Str::uuid();else $data['anonymous_token']=null;return $data;}
    private function validatedQuestion(Request $request): array {$data=$request->validate(['question'=>'required|string|max:2000','answer_type'=>'required|in:text,textarea,single_choice,multiple_choice,rating,yes_no,nps','options_text'=>'nullable|string','required'=>'nullable|boolean','position'=>'nullable|integer|min:0']);$data['options']=in_array($data['answer_type'],['single_choice','multiple_choice'],true)?collect(preg_split('/\\r\\n|\\r|\\n/',$data['options_text']??''))->map(fn($v)=>trim($v))->filter()->values()->all():null;$data['required']=$request->boolean('required');unset($data['options_text']);return $data;}
    private function analytics(Survey $survey): array {return $survey->questionRecords->mapWithKeys(function($q)use($survey){$values=$survey->responses->pluck('answers')->map(fn($a)=>$a[$q->id]??null)->filter(fn($v)=>$v!==null&&$v!=='');$flat=$values->flatMap(fn($v)=>is_array($v)?$v:[$v]);return [$q->id=>['responses'=>$values->count(),'counts'=>$flat->countBy()->sortDesc(),'average'=>in_array($q->answer_type,['rating','nps'])&&$values->count()?round($values->avg(),2):null]];})->all();}
    private function emailFor($record): ?string {$key=match($record::class){Contact::class=>'contact_id',Lead::class=>'lead_id',Account::class=>'account_id',Deal::class=>'deal_id'};return ContactEmail::where($key,$record->id)->value('email');}
    private function attachProfile(Survey $survey,string $module,int $id): void {$relation=match($module){'contacts'=>'contacts','leads'=>'leads','accounts'=>'accounts','deals'=>'deals'};$survey->{$relation}()->syncWithoutDetaching([$id]);}
    private function assertAvailable(Survey $survey): void {abort_unless($survey->status==='active',422,'This Survey is not active.');abort_if($survey->start_date && $survey->start_date->isFuture(),422,'This Survey has not started.');abort_if($survey->end_date && $survey->end_date->isPast(),422,'This Survey has closed.');}
    private function resolvePublicToken(string $token): array {$recipient=SurveyRecipient::with(['survey.questionRecords','respondent','response'])->where('token',$token)->first();if($recipient)return [$recipient->survey,$recipient];$survey=Survey::with('questionRecords')->where('allow_anonymous',true)->where('anonymous_token',$token)->firstOrFail();return [$survey,null];}
    private function authorizeRecipient(Survey $survey,SurveyRecipient $recipient): void {$this->authorizeBusiness($survey);abort_unless((int)$recipient->survey_id===(int)$survey->id,404);}
    private function deliver(SurveyRecipient $recipient,bool $reminder): void {$recipient->increment('delivery_attempts');try{$label=$reminder?'reminder':'invitation';Mail::raw("Survey {$label}: {$recipient->survey->name}: ".route('surveys.respond',$recipient->token),fn($mail)=>$mail->to($recipient->email)->subject(ucfirst($label).': '.$recipient->survey->name));$values=['status'=>'sent','sent_at'=>now(),'delivery_error'=>null];if($reminder){$values['last_reminded_at']=now();$values['reminder_count']=$recipient->reminder_count+1;}$recipient->update($values);}catch(\Throwable $e){$recipient->update(['status'=>'failed','delivery_error'=>Str::limit($e->getMessage(),2000)]);}}
    private function authorizeBusiness(Survey $survey): void {abort_unless($survey->business_id===auth()->user()->business_id,403);}
    private function authorizeChild(Survey $survey,SurveyQuestion $question): void {$this->authorizeBusiness($survey);abort_unless((int)$question->survey_id===(int)$survey->id,404);}
}
