<?php

namespace App\Services;

use App\Enums\LocationState;
use App\Enums\ProcessGeneralState;
use App\Helpers\DateHelper;
use App\Models\Modality;
use App\Models\Procedure;
use App\Models\Process;
use App\Models\Response;
use App\Models\ResponseType;
use App\Models\State;
use App\Models\UserComunity;
use App\Repositories\ProcessRepository;
use App\Repositories\StateRepository;
use App\Repositories\UserRepository;
use Config\Database;
use DateTime;
use Exception;
use PDO;

class ProcessService
{
    protected ProcedureService $procedureService;
    protected ProcessRepository $processRepository;
    protected LocationService $locationService;
    protected StateRepository $stateRepository;
    protected UserRepository $userRepository;

    public function __construct()
    {
        $this->procedureService = new ProcedureService();
        $this->processRepository = new ProcessRepository();
        $this->locationService = new LocationService();
        $this->stateRepository = new StateRepository();
        $this->userRepository = new UserRepository();
    }

    public function createProcess($data, $session)
    {
        if (empty($data['procedure_id']) || empty($data['folios']) || empty($data['subject'])) {
            throw new Exception("Faltan llenar datos necesarios.");
        }

        $isOther = ($data['procedure_id'] === 'other');

        if ($isOther) {
            if (empty($data['other_procedure'])) {
                throw new Exception("Debe especificar el nombre del procedimiento cuando selecciona 'OTROS'.");
            }

            $procedure = null;
            $route = null;
        } else {
            $procedure = Procedure::find($data['procedure_id']);
            if (!$procedure) {
                throw new Exception("Procedimiento no encontrado.");
            }

            $route = $this->procedureService->getOrderedRoutes($procedure);
            if (empty($route)) {
                throw new Exception("El procedimiento no cuenta con una ruta definida. Consulte con el administrador para agregar la ruta.");
            }
        }

        $user_id = null;
        $responsible_id = (int)$session['user_id'];

        if ($session['role'] !== 'COMUNITY') {
            $documento = null;

            if (!empty($data['dni'])) {
                $documento = trim($data['dni']);
            } elseif (!empty($data['ruc'])) {
                $documento = trim($data['ruc']);
            }

            if (!$documento) {
                throw new Exception("Debe ingresar un DNI o RUC válido: " + $documento);
            }

            if (strlen($documento) === 8) {
                $comunity_user = $this->userRepository->findComunityUserByDni($documento);
            } elseif (strlen($documento) === 11) {
                $comunity_user = $this->userRepository->findComunityUserByRuc($documento);
            } else {
                throw new Exception("Debe ingresar un DNI o RUC válido.");
            }

            if (!$comunity_user) {
                throw new Exception("No se encontró un usuario con el documento ingresado.");
            }

            $user_id = $comunity_user['id'];
        } else {
            $comunity_user = UserComunity::findBy('user_id', (int)$session['user_id']);
            if (!$comunity_user) {
                throw new Exception("Usuario de comunidad educativa no encontrado.");
            }
            $user_id = $comunity_user->id;
        }

        $submissionModalityId = null;
        if ($procedure) {
            $modality = $procedure->modality();
            if (strtoupper($modality->description) === 'MIXTO') {
                $submissionModalityId = self::inferSubmissionModalityId($session);
            } else {
                $submissionModalityId = $modality->id;
            }
        } else {
            $submissionModalityId = self::inferSubmissionModalityId(session: $session);
        }

        $num_exp = $this->processRepository->generateExpedientNumber($submissionModalityId);
        $key = $this->processRepository->generateUniqueKey();

        $process = new Process();
        $process->num_exped = $num_exp;
        $process->entry_date = DateHelper::getValidEntryDate();
        $process->period = date('Y');
        $process->folios = $data['folios'];
        $process->user_id = $user_id;
        $process->key = $key;
        $process->subject = $data['subject'];
        $process->rationale = $data['rationale'] ?? null;
        $process->reset = 1;
        $process->active = 1;
        $process->deleted_at = null;
        $process->last_location = null;
        $process->resolved = 0;
        $process->responsible_id = $responsible_id;
        $process->general_state = ProcessGeneralState::EN_PROCESO;
        $process->re_entered = 0;

        if ($isOther) {
            $process->procedure_id = null;
            $process->custom_procedure = trim($data['other_procedure']);
            $process->derivation_mode = "MANUAL";
        } else {
            $process->procedure_id = $procedure->id;
            $process->custom_procedure = null;
            $process->derivation_mode = "AUTOMATICO";
        }

        $process->save();

        return $process;
    }

    private static function inferSubmissionModalityId($session)
    {
        if ($session['role'] === 'COMUNITY') {
            return Modality::findBy('description', 'VIRTUAL')->id;
        } else {
            return Modality::findBy('description', 'PRESENCIAL')->id;
        }
    }

    public function updateLastLocation(int $processId, int $lastLocationId)
    {
        return $this->processRepository->updateLastLocation($processId, $lastLocationId);
    }

    public function getDistinctPeriods()
    {
        return $this->processRepository->getDistinctPeriods();
    }

    public function searchProcess($data)
    {
        return $this->processRepository->searchProcess($data);
    }

    public function getProcessesByUserId($userId)
    {
        return $this->processRepository->getByUserId($userId);
    }

    public function getProcessesResolvedByUserId($userId)
    {
        return $this->processRepository->getResolvedByUserId($userId);
    }

    public function getDerivedProcessesByGroup($groupId, $subgroupId = null)
    {
        return $this->processRepository->getDerivedProcessesByGroup($groupId, $subgroupId);
    }


    public function handleResponse($process_id, $resolution_number, $comment, $files, $response_type)
    {
        $process = Process::find($process_id);
        $currentLocation = $process->lastLocation();

        if (!$process || !$currentLocation) {
            throw new Exception("Trámite no encontrado.");
        }

        $isResponse = $response_type === 'RESPONDER';

        $responseType = $isResponse
            ? ResponseType::getIdByName('RESOLUCION')
            : ResponseType::getIdByName('PROVEIDO');


        $response = new Response();
        $response->process_id = $process_id;
        $response->user_id = UserService::getUser()->id;
        $response->resolution_number = $resolution_number;
        $response->comment = $comment;
        $response->response_type_id = $responseType;
        $response->created_at = DateHelper::adjustToBusinessDate(date('Y-m-d H:i:s'))->format('Y-m-d H:i:s');
        $response->active = 1;
        $response->is_read = 0;

        if (!empty($files['name'][0])) {
            $uploadedFile = AttachmentService::uploadFiles($currentLocation->id, $process->num_exped, $files);
            $response->file_name = $uploadedFile['file_name'];
            $response->file_path = $uploadedFile['file_path'];
        }

        $response->save();

        if ($isResponse) {
            $currentLocation->state = LocationState::RESUELTO;
            $this->processRepository->markAsResolved($process->id);
        } else {
            $currentLocation->state = LocationState::OBSERVADO;
            $process->general_state = ProcessGeneralState::OBSERVADO;

            $procedure = $process->procedure();
            $process->correction_deadline = self::addCorrectionDeadLine(date('Y-m-d'), $procedure->correction_days);
            $process->save();
        }

        if ($comment) {
            $currentLocation->comment = $comment;
        }

        $currentLocation->attended_date = DateHelper::adjustToBusinessDate(date('Y-m-d H:i:s'))->format('Y-m-d H:i:s');
        $currentLocation->save();

        $userComunity = UserComunity::find($process->user_id);
        if ($userComunity->user_id) {
            if ($isResponse) {
                $message = "El trámite {$process->num_exped} ha sido resuelto. Por favor, visualice su resolución.";
            } else {
                $procedure = $process->procedure();
                $message = "El trámite {$process->num_exped} ha sido observado. Motivo: {$comment}. Puede subsanar las observaciones en un plazo de {$procedure->correction_days} días hábiles.";
            }

            NotificationService::createNotification($process->id, $message, $userComunity->user_id, null, null);
        }

        MailService::sendResponseNotification($userComunity->email, $process->num_exped);
    }


    public function correctProcess($id, $data, $session)
    {
        if (empty($data['folios']) || empty($data['subject'])) {
            throw new Exception("Faltan llenar datos necesarios.");
        }

        $process = Process::find($id);
        if (!$process) {
            throw new Exception("Trámite no encontrado.");
        }

        $userId = $session['user_id'];
        $user = UserComunity::findBy('user_id', $userId);

        if ($session['role'] == "COMUNITY" && $process->user_id !== $user->id) {
            throw new Exception("No tienes permisos para corregir este trámite.");
        }

        if (!empty($process->correction_deadline) && strtotime(date('Y-m-d')) > strtotime($process->correction_deadline)) {
            throw new Exception("El plazo para corregir este trámite ha vencido.");
        }

        $lastLocation = $process->lastLocation();
        if (!$lastLocation) {
            throw new Exception("No se encontró la última ubicación del trámite.");
        }

        if (!empty($data['procedure_id']) && $data['procedure_id'] !== 'other') {
            $procedure = Procedure::find($data['procedure_id']);
            if (!$procedure) {
                throw new Exception("Procedimiento no encontrado.");
            }

            $process->procedure_id = $procedure->id;
            $process->custom_procedure = null;
        } else {
            if (empty($data['custom_procedure'])) {
                throw new Exception("Debe especificar el procedimiento al elegir 'OTROS'.");
            }

            $process->procedure_id = null;
            $process->custom_procedure = trim($data['custom_procedure']);
        }

        $process->folios = $data['folios'];
        $process->subject = $data['subject'];
        $process->rationale = $data['rationale'] ?? null;
        $process->general_state = ProcessGeneralState::EN_PROCESO;
        $process->re_entered = 1;
        $process->save();

        $lastLocation->derived_date = DateHelper::getValidEntryDate();
        $lastLocation->state = LocationState::PENDIENTE;
        $lastLocation->attended_date = null;
        $lastLocation->save();

        return $process;
    }


    // public function correctProcess($id, $data, $session)
    // {
    //     if (empty($data['procedure_id']) || empty($data['folios']) || empty($data['subject'])) {
    //         throw new Exception("Faltan llenar datos necesarios.");
    //     }

    //     $process = Process::find($id);
    //     if (!$process) {
    //         throw new Exception("Trámite no encontrado.");
    //     }

    //     $userId = $session['user_id'];
    //     $user = UserComunity::findBy('user_id', $userId);

    //     if ($session['role'] == "COMUNITY" && $process->user_id !== $user->id) {
    //         throw new Exception("No tienes permisos para corregir este trámite.");
    //     }

    //     if (strtotime(date(format: 'Y-m-d')) > strtotime($process->correction_deadline)) {
    //         throw new Exception("El plazo para corregir este trámite ha vencido.");
    //     }

    //     $lastLocation = $process->lastLocation();
    //     if (!$lastLocation) {
    //         throw new Exception("No se encontró la última ubicación del trámite.");
    //     }

    //     $procedure = Procedure::find($data['procedure_id']);
    //     if (!$procedure) {
    //         throw new Exception("Procedimiento no encontrado.");
    //     }


    //     $process->procedure_id = $procedure->id;
    //     $process->folios = $data['folios'];
    //     $process->subject = $data['subject'];
    //     $process->rationale = $data['rationale'];
    //     $process->general_state = ProcessGeneralState::EN_PROCESO;
    //     $process->re_entered = 1;
    //     $process->save();

    //     $lastLocation->derived_date = DateHelper::getValidEntryDate();
    //     $lastLocation->state = LocationState::PENDIENTE;
    //     $lastLocation->attended_date = null;
    //     $lastLocation->save();        

    //     return $process;
    // }

    public static function addCorrectionDeadLine($startDate, $days)
    {
        $date = new DateTime($startDate);
        $addedDays = 0;
        $year = $date->format('Y');
        $holidays = DateHelper::getPeruHolidays($year);

        while ($addedDays < $days) {
            $date->modify('+1 day');

            if ($date->format('Y') !== $year) {
                $year = $date->format('Y');
                $holidays = array_merge($holidays, DateHelper::getPeruHolidays($year));
            }

            $isWeekend = in_array($date->format('N'), [6, 7]);
            $isHoliday = in_array($date->format('Y-m-d'), $holidays);

            if (!$isWeekend && !$isHoliday) {
                $addedDays++;
            }
        }

        return $date->format('Y-m-d');
    }

    public function getElapsedPercentage(Process $process)
    {
        $procedure = $process->procedure ?? $process->procedure();

        $diasPlazo = ($procedure && isset($procedure->time))
            ? (int)$procedure->time
            : 10; //DIAS DE PLAZO POR DEFECTO

        $fechaInicio = new DateTime($process->entry_date);
        $hoy = new DateTime();

        $diasHabiles = DateHelper::countBusinessDays($fechaInicio, $hoy);

        return ($diasPlazo > 0)
            ? min(($diasHabiles / $diasPlazo) * 100, 100)
            : 0;
    }


    // public function getElapsedPercentage(Process $process)
    // {
    //     $diasPlazo = $process->procedure ? $process->procedure->time : $process->procedure()->time; //SI NO TIENE PRECARGADO SU PROCESS LO BUSCA
    //     $fechaInicio = new DateTime($process->entry_date);
    //     $hoy = new DateTime();

    //     $diasHabiles = DateHelper::countBusinessDays($fechaInicio, $hoy);

    //     return ($diasPlazo > 0) ? ($diasHabiles / $diasPlazo) * 100 : 0;
    // }


    public function getState(Process $process, ?array $preloadedStates = null)
    {
        $porcentaje = $this->getElapsedPercentage($process);

        if ($preloadedStates !== null) {
            foreach ($preloadedStates as $s) {
                if ($porcentaje >= $s->start_percentage && $porcentaje <= $s->end_percentage) {
                    return $s;
                }
            }
            return null;
        }

        return $this->stateRepository->getStateByPercentage($porcentaje);
    }

    public function enrichSingle(Process $process, ?array $preloadedStates = null): Process
    {
        $process->state = $process->isResolved()
            ? null
            : $this->getState($process, $preloadedStates);

        return $process;
    }

    public function enrichWithState(array $processes): array
    {
        $states = $this->stateRepository->preloadStates();

        foreach ($processes as $p) {
            $this->enrichSingle($p, $states);
        }

        return $processes;
    }

    //--------------------------------------------------------------------------------------
    public function getUnresolvedProcessesByPeriod($period): array
    {
        return $this->processRepository->getUnresolvedProcessesByPeriod($period);
    }

    public function searchByName(array $filters): array
    {
        return $this->processRepository->searchByName($filters);
    }

    public function searchByDni($filters)
    {
        return $this->processRepository->searchByDni($filters);
    }

    public function searchByDateRange(array $filters): array
    {
        return $this->processRepository->searchByDateRange($filters);
    }

    public function searchByPriority($filters) // YA NO EXISTIRÁ EL STATE_ID
    {
        return $this->processRepository->searchByPriority($filters);
    }

    public function searchByProcedure($filters)
    {
        return $this->processRepository->searchByProcedure($filters);
    }

    public function searchByDerivationDate($filters)
    {
        return $this->processRepository->searchByDerivationDate($filters);
    }

    public function searchByState($filters = [])
    {
        $rows = $this->processRepository->searchByState($filters);
        $states = $this->stateRepository->preloadStates();

        $resultados = [];

        $estadoFiltro = $filters['state'] ?? null;

        foreach ($rows as $row) {
            $entryDate = new DateTime($row['entry_date']);
            $today = new DateTime();
            $diasTranscurridos = DateHelper::countBusinessDays($entryDate, $today);

            $totalDias = (int) $row['total_days'];
            if ($totalDias <= 0) {
                continue;
            }

            $porcentaje = ($totalDias > 0) ? ($diasTranscurridos / $totalDias) * 100 : 0;
            //$state = $this->stateRepository->getStateByPercentage($porcentaje);

            $state = null;
            foreach ($states as $s) {
                if ($porcentaje >= $s->start_percentage && $porcentaje <= $s->end_percentage) {
                    $state = $s;
                    break;
                }
            }

            if (!$state || ($estadoFiltro && strtoupper($state->description) !== $estadoFiltro)) {
                continue;
            }

            $resultados[] = [
                'expediente' => $row['num_exped'],
                'asunto' => $row['subject'],
                'procedimiento' => $row['procedimiento'],
                'grupo' => $row['grupo'],
                'subgrupo' => $row['subgrupo'],
                'area' => $row['area'],
                'tipo_grupo' => $row['tipo_grupo'],
                'fecha_ingreso' => $row['entry_date'],
                'dias_transcurridos' => $diasTranscurridos,
                'estado' => $state->description,
                'color' => $state->color
            ];
        }

        return $resultados;
    }

    public function searchByProcessNumber(array $filters): array
    {
        return $this->processRepository->searchByProcessNumber($filters);
    }

    public function groupStateResultsByHierarchy(array $stateResults): array
    {
        return $this->processRepository->groupStateResultsByHierarchy($stateResults);
    }
}
