<?php

namespace App\Http\Controllers\API;

use App\Exports\DownloadHelper;
use App\Exports\TaskExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\Task\AssignRequest;
use App\Http\Requests\API\Task\BatchCreateItemRules;
use App\Http\Requests\API\Task\BatchCreateRequest;
use App\Http\Requests\API\Task\CreateOrUpdateRequest;
use App\Http\Requests\API\Task\LinkContainerRequest;
use App\Http\Resources\API\TaskDetailResource;
use App\Http\Resources\API\TaskResource;
use App\Imports\TaskImport;
use App\Models\Approval;
use App\Models\Container;
use App\Models\Enums\ActionObjectType;
use App\Models\Enums\FileObjectType;
use App\Models\Enums\ObjectAction;
use App\Models\Enums\TaskStatus;
use App\Models\Library;
use App\Models\Project;
use App\Models\Requirement;
use App\Models\Task;
use App\Models\TaskContainer;
use App\Repositories\ActionRepository;
use App\Repositories\CustomFieldRepository;
use App\Services\File\FileAssociationService;
use App\Services\History\ModelChangeDetector;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;

class TaskController extends Controller
{
    use DownloadHelper;

    /**
     * Display a listing of the resource.
     */
    public function index(Request $request)
    {
        $tasks= $this->getTasks($request);
        return TaskResource::collection($tasks);
    }

    public function publicSearch(Request $request){
        $tasks=$this->getTasks($request);
        return TaskResource::collection($tasks);
    }

    protected function getTasks(Request $request){
        $pageSize=$request->get('page_size') ?? 10;
        $sort=$request->input('sort','desc');
        $tasks = Task::query()
            ->where("parent_id", 0)
            ->when($request->approval_status, fn($query) => $query->where("approval_status", $request->approval_status))
            ->with(['children', 'assignTo', 'createdBy'])
            ->filter($request->all())
            ->allowed()
            ->orderBy('created_at',$sort)
            ->paginate($pageSize);

        return $tasks;
    }

    public function export(Request $request)
    {
        return $this->downloadExcelHelper(
            new TaskExport(),
            "task",
            $request->get("extension"),
        );
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(FileAssociationService $service, CreateOrUpdateRequest $request, CustomFieldRepository $customFieldRepo)
    {
        $isAction=true;
        $projectId=$request->get("project_id");
        $project = Project::allowed($projectId,$isAction)->find($projectId);
        if($project===null||is_null($project)){
            return $this->badRequest('Permission denied or project not found. Please contact the administrator.');
        };

        $requirement = $request->has('requirement_id')
            ? Requirement::query()->findOrFail($request->get("requirement_id"))
            : null;

        $service->check(
            $request->get("file_ids", []),
            FileObjectType::TASK,
            $request->get("file_uuid"),
        );

        $formData = [
            ...$request->all(),
            'company_id' => Auth::user()->company_id,
            'created_by' => Auth::id(),
            'whitelist' => $request->whitelist ? sprintf(",%s,", implode(',', $request->whitelist)) : null,
            'asset_id' => $requirement?->asset_id,
            'description' => $request->description? (new \App\Services\File\ImageUrlService)->interceptImageUrl($request->description) : null,
            'requirement_group_id'=> $requirement?->requirement_group_id,
        ];

        if ($request->has("naming_rule_id") && $request->get("naming_rule_id") > 0) {
            $keys = $customFieldRepo->keysByGroup($request->get("naming_rule_id"));
            $formData['naming_rules'] = $request->only($keys);
        }

        $task = Task::create($formData);

        ActionRepository::createByTask($task, ObjectAction::CREATED);

        $service->association($task->id);

        return $this->created();
    }

    public function import(Request $request)
    {
        Excel::import(new TaskImport(), $request->file("file"));

        return $this->created();
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        $task = Task::query()->allowed($id)->with([
            'containers'
        ])->findOrFail($id);

        return new TaskDetailResource($task);
    }

    public function start(Request $request, string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };
        $task->status = TaskStatus::DOING->value;
        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task,
            ObjectAction::STARTED,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();
    }

    public function pause(Request $request, string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $task->status = TaskStatus::PAUSE->value;
        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task, ObjectAction::PAUSED,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();
    }

    public function closed(Request $request, string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $task->status = TaskStatus::CLOSED->value;
        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task, ObjectAction::CLOSED,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();
    }

    public function done(Request $request, string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $task->fill([
            'status' => TaskStatus::DONE->value,
            'finished_by' => Auth::user()->id,
            'finished_at' => Carbon::now(),
        ]);

        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task, ObjectAction::DONE,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();
    }

    public function cancel(Request $request, string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $task->fill([
            'status' => TaskStatus::CANCEL->value,
            'canceled_by' => Auth::user()->id,
            'canceled_at' => Carbon::now(),
        ]);

        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task, ObjectAction::CANCELED,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();
    }

    public function  wait(Request $request,string $id){
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };
        $task->status=TaskStatus::WAIT->value;

        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task, ObjectAction::WAITED,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();

    }

    public function assign(AssignRequest $request,string $id){
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };
        $task->fill([
            'assign'=>$request->get('assign'),
              ...$request->all(),
        ]);

        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask(
            $task, ObjectAction::ASSIGNED,
            $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
            objectChanges: $changes
        );

        return $this->noContent();
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(CreateOrUpdateRequest $request, string $id, CustomFieldRepository $customFieldRepo)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $requirement = $request->has('requirement_id') && $task->requirement_id != $request->get('requirement_id')
            ? Requirement::query()->findOrFail($request->get("requirement_id"))
            : null;

        $formData = [
            ...$request->except('created_by'),
            'whitelist' => $request->whitelist ? sprintf(",%s,", implode(',', $request->whitelist)) : null,
            'description' => $request->description? (new \App\Services\File\ImageUrlService)->interceptImageUrl($request->description) : null,
            'asset_id' => $requirement?->asset_id,
        ];

        if ($request->has("naming_rule_id") && $request->get("naming_rule_id") > 0) {
            $keys = $customFieldRepo->keysByGroup($request->get("naming_rule_id"));
            $formData['naming_rules'] = $request->only($keys);
        }

        $task->fill($formData);
        $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
        $task->save();

        ActionRepository::createByTask($task, ObjectAction::EDITED, objectChanges: $changes);

        return $this->noContent();
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $task->delete();

        Approval::query()->where('object_type','task')->where('object_id',$id)->delete();

        ActionRepository::createByTask($task, ObjectAction::DELETED);

        return $this->noContent();
    }

    public function batchStore(BatchCreateRequest $request, CustomFieldRepository $customFieldRepo)
    {
        $isAction=true;
        $projectId=$request->get("project_id");
        $project = Project::allowed($projectId,$isAction)->find($projectId);
        if($project===null||is_null($project)){
            return $this->badRequest('Permission denied or project not found. Please contact the administrator.');
        };
        $parsedItems = [];
        $previousItem = [];
        foreach ($request->items as $index => $item) {
            if ($index == 0) {
                $newItem = $item;
            } else {
                $newItem = [];
                foreach ($item as $k => $v) {
                    $newItem[$k] = $v == "ditto" ? data_get($previousItem, $k) : $v;
                }
            }

            $previousItem = $newItem;
            $parsedItems[] = $newItem;
        }

        $itemRules = new BatchCreateItemRules();
        foreach ($parsedItems as $index => $item) {
            $rules = $itemRules->rules($item);

            try {
                $validator = Validator::make($item, $rules);
                if ($validator->fails()) {
                    return $this->badRequest(sprintf("line: %d, %s", $index + 1, $validator->errors()->first()));
                }
            } catch (\Exception $e) {
                return $this->badRequest(sprintf("line: %d, %s", $index + 1, $e->getMessage()));
            }
        }

        foreach ($parsedItems as $item) {
            $requirement=$item['requirement_id']>0 ? Requirement::query()->findOrFail($item['requirement_id']) : null;
            $item["whitelist"]=!empty($item['whitelist']) ? sprintf(",%s,", implode(',',$item['whitelist'])) : null;

            $namingRuleId = data_get($item, "naming_rule_id", 0);
            if ($namingRuleId > 0) {
                $keys = $customFieldRepo->keysByGroup($namingRuleId);
                $item['naming_rules'] = collect($item)->only($keys)->toArray();
            }

            $task = Task::query()->create([
                ...$item,
                'project_id' => $project->id,
                'parent_id' => $request->parent_id,
                'company_id' => Auth::user()->company_id,
                'created_by' => Auth::id(),
                'asset_id' => $requirement?->asset_id,
                'requirement_group_id'=> $requirement?->requirement_group_id,
            ]);

            ActionRepository::createByTask($task, ObjectAction::CREATED);
        }

        return $this->noContent();
    }

    /**
     * 容器链接
     *
     * @param LinkContainerRequest $request
     * @param string $id
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
     */
    public function linkContainer(LinkContainerRequest $request, string $id)
    {
        $isAction=true;
        $task = Task::query()->allowed($id,$isAction)->find($id);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $libraryIds = Library::query()->allowed()->where("project_id", $task->project_id)->pluck("id");
        if ($libraryIds->isEmpty()) {
            return $this->badRequest("No container exists for the current task item");
        }

        $containers = Container::query()
            ->allowed()
            ->whereIn("library_id", $libraryIds->toArray())
            ->whereIn("id", $request->get("container_ids", []))
            ->get(['id']);

        if (! $containers) {
            return $this->badRequest("Please select a valid container");
        }

        foreach ($containers as $container) {
            TaskContainer::query()->firstOrCreate([
                'container_id' => $container->id,
                'task_id' => $task->id
            ]);
        }

        return $this->noContent();
    }


    public function unlinkContainer(string $id)
    {
        $taskContainer = TaskContainer::query()->findOrFail($id);
        $taskId=$taskContainer->task_id;
        $isAction=true;
        $task = Task::query()->allowed($taskId,$isAction)->find($taskId);
        if($task===null||is_null($task)){
            return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
        };

        $taskContainer->delete();

        return $this->noContent();
    }

    /**
     * 待关联的容器
     *
     * @param Request $request
     * @param string $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function containerToBeLinked(Request $request, string $id)
    {
        $task = Task::query()->allowed($id)->findOrFail($id);

        $libraryIds = Library::query()->allowed()->where("project_id", $task->project_id)->pluck("id");
        if ($libraryIds->isEmpty()) {
            return $this->badRequest("No container exists for the current task item");
        }

        $containers = Container::query()
            ->allowed()
            ->leftJoin("task_container", "containers.id", "=", "task_container.container_id")
            ->where($request->only(['library_id']))
            ->whereIn("library_id", $libraryIds->toArray())
            ->whereNull("task_container.id")
            ->selectRaw("containers.id, containers.name")
            ->get();

        return $this->success([
            'data' => $containers
        ]);
    }
}