<?php

namespace App\Services;

use Carbon\Carbon;
use App\Models\Resource;
use App\Models\SMTPMails;
use App\Models\ScheduledTask;
use App\Repositories\ScheduledTaskRepository;

class ScheduledReportService
{
    private $moduleService;
    private $groupService;
    private $scheduledTaskRepository;

    public function __construct(
        ModuleService $moduleService,
        GroupService $groupService,
        ScheduledTaskRepository $scheduledTaskRepository
    ) {
        $this->moduleService           = $moduleService;
        $this->groupService            = $groupService;
        $this->scheduledTaskRepository = $scheduledTaskRepository;
    }

    /**
     * Retrieves a list of all scheduled tasks, ordered by the most recently created.
     *
     * This method queries the `ScheduledTask` model and returns a collection of scheduled tasks,
     * sorted by their creation date in descending order.
     *
     * @tag srm_schdular
     */
    public function getScheduledReports()
    {
        return ScheduledTask::orderBy('created_at', 'desc')->get();
    }

    /**
     * Stops the specified scheduled task by updating its status.
     *
     * This method updates the `last_status` of the given `ScheduledTask` to "stopped"
     * and records the ID of the user who made the change.
     *
     * @tag srm_schdular
     */
    public function stop($scheduledTask)
    {
        $this->checkScheduledReportCreatorPermission($scheduledTask);

        $scheduledTask->update([
            'last_status' => 'stopped',
            'edited_by'   => auth()->user()->user_ID
        ]);
    }

    /**
     * Resumes the specified scheduled task by updating its status.
     *
     * This method updates the `last_status` of the given `ScheduledTask` to "active"
     * and records the ID of the user who made the change.
     *
     * @tag srm_schdular
     */
    public function resume($scheduledTask)
    {
        $this->checkScheduledReportCreatorPermission($scheduledTask);

        $scheduledTask->update([
            'last_status' => 'active',
            'edited_by'   => auth()->user()->user_ID
        ]);
    }

    /**
     * Retrieves the task history for a specified scheduled task.
     *
     * This method retrieves the task history records related to a specific `ScheduledTask`,
     * ordered by the most recent `end_sending_date`.
     *
     * @tag srm_schdular
     */
    public function show($scheduledTask)
    {
        return $scheduledTask->taskHistory()->orderBy('end_sending_date','desc')->get();
    }


    /**
     * Stores a new scheduled task in the system.
     *
     * This method validates the incoming request, checks if the default SMTP settings are configured,
     * and then stores a new scheduled task. If an error occurs, an error message is returned.
     *
     * @tag srm_schdular
     */
    public function store($request)
    {

        if (is_null(SMTPMails::defaultSmtpMail()->first())) {
            return $this->errorMessage(trans('scheduled-report.messages.smtp-not-found'));
        }

        try {
            $this->scheduledTaskRepository->store($request->validated());
            return $this->successMessage();
        } catch (\Exception $e) {
            return $this->errorMessage($e->getMessage());
        }

    }

    /**
     * Updates an existing scheduled task in the system.
     *
     * This method validates the request and updates the specified scheduled task with new data.
     * If an error occurs, an error message is returned.
     *
     * @tag srm_schdular
     */
    public function update($scheduledTask, $request)
    {
        try {
            $this->scheduledTaskRepository->update($scheduledTask, $request->validated());
            return $this->successMessage();
        } catch (\Exception $e) {
            return $this->errorMessage($e->getMessage());
        }

    }

    /**
     * Deletes a scheduled task from the system.
     *
     * This method checks permissions and deletes the specified `ScheduledTask`.
     * It also deletes associated resources and logs the user who deleted the task.
     *
     * @tag srm_schdular
     */
    public function destroy($scheduledTask)
    {
        $this->checkScheduledReportCreatorPermission($scheduledTask);

        $scheduledTask->update([
            'deleted_by' => auth()->user()->user_ID
        ]);

        $scheduledTask->resources()->delete();
        $scheduledTask->delete();
    }

    /**
     * Checks if the current user has permission to create or modify a scheduled report.
     *
     * This method ensures that the user is either an admin or the owner of the scheduled task before allowing
     * further actions. If the user does not have the necessary permissions, a 403 error is thrown.
     *
     * @tag srm_schdular
     */
    private function checkScheduledReportCreatorPermission(ScheduledTask $scheduledTask)
    {

        if (!auth()->user()->isAdminOrOwner()) {
            abort(403, trans('report.unauthorized'));
        }

    }


    /**
     * Checks if the current user has access to the specified report.
     *
     * This method ensures that the user has the necessary permissions to access the report. The user must either
     * be an admin or owner, or the report must be public, or the user must belong to a group with specific permissions
     * to access the report.
     *
     * @tag srm_schdular
     */
    private function checkReportAccessPermission(Resource $report)
    {

        if (
            !(auth()->user()->isAdminOrOwner() || strtolower($report->access_control_type) == "public" ||
                in_array(auth()->user()->group_id, $report->resource_permissions->pluck("group_ID")->toArray()))
        ) {
            abort(403, trans('report.access_unautorized'));
        }

    }

    /**
     * Returns a success message for a successfully saved scheduled task.
     *
     * This method generates a success message, including a link to the "Manage scheduled reports" page.
     *
     * @tag srm_schdular
     */
    private function successMessage()
    {
        $managePageUrl = route('scheduled-reports.index');

        return [
            "type" => "success",
            "body" => "Scheduled task is saved successfully, <a href='" . $managePageUrl . "'>Click Here</a> to return back to the Manage scheduled reports page"
        ];
    }

    /**
     * Returns an error message for a failed scheduled task save.
     *
     * This method generates an error message, including details of the error that occurred while saving the scheduled task.
     *
     * @tag srm_schdular
     */
    private function errorMessage($error)
    {
        return [
            "type" => "failed",
            "body" => "Error in saving the scheduled task: $error"
        ];

    }

    /**
     * Calculates the next sending date based on the specified frequency.
     *
     * This method calculates the next sending date based on the task frequency: hourly, daily, weekly, or monthly.
     * The method uses the provided start date, sending time, weekly day, and monthly day to calculate the next date.
     *
     * @tag srm_schdular
     */
    public static function caluclateNextSendingDate($frequency, $beginDate, $sendingTime, $sendingWeeklyDay, $sendingMonthlyDay)
    {

        switch ($frequency) {
            case 'hourly':
                return static::calculateHourlySendingDate($beginDate);
                break;
            case 'daily':
                return static::calculateDailySendingDate($beginDate, $sendingTime);
                break;
            case 'weekly':
                return static::calculateWeeklySendingDate($beginDate, $sendingTime, $sendingWeeklyDay);
                break;
            case 'monthly':
                return static::calculateMonthlySendingDate($beginDate, $sendingTime, $sendingMonthlyDay);
                break;
            default:
                break;
        }

    }

    /**
     * Recalculates the next sending date for an existing scheduled task.
     *
     * This method recalculates the next sending date based on the frequency and configuration of the specified `ScheduledTask`.
     *
     * @tag srm_schdular
     */
    public static function recaluclateNextSendingDate($task)
    {

        switch ($task->frequency) {
            case 'hourly':
                return static::calculateHourlySendingDate($task->begin_date);
                break;
            case 'daily':
                return static::calculateDailySendingDate($task->begin_date, $task->sending_time);
                break;
            case 'weekly':
                return static::calculateWeeklySendingDate($task->begin_date, $task->sending_time, $task->sending_weekly_day);
                break;
            case 'monthly':
                return static::calculateMonthlySendingDate($task->begin_date, $task->sending_time, $task->sending_monthly_day);
                break;
            default:
                break;
        }

    }

    /**
     * Calculates the next hourly sending date based on the provided start date.
     *
     * This method calculates the next scheduled sending date by adding one hour to the `beginDate`,
     * setting the minutes and seconds to 0.
     *
     * @tag srm_schdular
     */
    private static function calculateHourlySendingDate($beginDate)
    {
        $baseDate = static::getBaseDate($beginDate);
        return $baseDate->addHour()->minute(0)->second(0)->toDateTimeString();
    }


    /**
     * Calculates the next daily sending date based on the provided start date and sending time.
     *
     * This method calculates the next scheduled sending date, ensuring the task occurs at the specified
     * `sendingTime` either today (if not past) or the next day.
     *
     * @tag srm_schdular
     */
    private static function calculateDailySendingDate($beginDate, $sendingTime)
    {
        $baseDate = static::getBaseDate($beginDate);
        return $baseDate->hour($sendingTime) > now()
        ? $baseDate->startOfDay()->hour($sendingTime)->toDateTimeString()
        : $baseDate->addDay()->startOfDay()->hour($sendingTime)->toDateTimeString();
    }


    /**
     * Calculates the next weekly sending date based on the provided start date, sending time, and weekly day.
     *
     * This method calculates the next scheduled sending date for a weekly task. The method ensures the
     * task occurs on the specified `sendingWeeklyDay` and at the specified `sendingTime`.
     *
     * @tag srm_schdular
     */
    private static function calculateWeeklySendingDate($beginDate, $sendingTime, $sendingWeeklyDay)
    {
        $baseDate = static::getBaseDate($beginDate);

        return $baseDate->is($sendingWeeklyDay) && Carbon::createFromTime($sendingTime)->hour > now()->hour
        ? $baseDate->startOfDay()->hour($sendingTime)->toDateTimeString()
        : $baseDate->next($sendingWeeklyDay)->hour($sendingTime)->toDateTimeString();
    }


    /**
     * Calculates the next monthly sending date based on the provided start date, sending time, and monthly day.
     *
     * This method calculates the next scheduled sending date for a monthly task, either based on a specific
     * day of the month (`sendingMonthlyDay`) or the last day of the month.
     *
     * @tag srm_schdular
     */
    private static function calculateMonthlySendingDate($beginDate, $sendingTime, $sendingMonthlyDay)
    {
        $baseDate = static::getBaseDate($beginDate);

        if ($sendingMonthlyDay != "last") {

            if ($baseDate->day < $sendingMonthlyDay) {
                return $baseDate->day($sendingMonthlyDay)->startOfDay()
                    ->hour($sendingTime)
                    ->toDateTimeString();
            } elseif ($baseDate->day == $sendingMonthlyDay && Carbon::createFromTime($sendingTime)->hour > now()->hour) {
                return $baseDate->startOfDay()->hour($sendingTime)->toDateTimeString();
            } else {
                return $baseDate->addMonthNoOverflow()->day($sendingMonthlyDay)->startOfDay()
                    ->hour($sendingTime)->toDateTimeString();
            }

        }

        if ($baseDate->isLastOfMonth() && Carbon::createFromTime($sendingTime)->hour <= now()->hour) {
            return $baseDate->addMonthNoOverflow()->endOfMonth()->hour($sendingTime)
                ->minute(0)->second(0)->toDateTimeString();
        } else {
            return $baseDate->endOfMonth()->startOfDay()
                ->hour($sendingTime)
                ->toDateTimeString();
        }

    }


    /**
     * Retrieves the base date for scheduling, either the provided start date or the current date.
     *
     * This method checks if the `beginDate` is in the future. If it is, it returns that date; otherwise,
     * it returns the current date and time.
     *
     * @tag srm_schdular
     */
    private static function getBaseDate($beginDate)
    {
        return Carbon::parse($beginDate) > now()->startOfDay() ? Carbon::parse($beginDate) : now();
    }


    /**
     * Maps a string day of the week to its corresponding Carbon constant.
     *
     * This method converts a string representation of a weekday to its equivalent Carbon constant.
     *
     * @tag srm_schdular
     */
    private function mapStringDaysToCarbon($weekDay)
    {

        switch ($weekDay) {
            case 'monday':
                return Carbon::MONDAY;
                break;
            case 'tuesday':
                return Carbon::TUESDAY;
                break;
            case 'wednesday':
                return Carbon::WEDNESDAY;
                break;
            case 'thrusday':
                return Carbon::THURSDAY;
                break;
            case 'friday':
                return Carbon::FRIDAY;
                break;
            case 'saturday':
                return Carbon::SATURDAY;
                break;
            case 'sunday':
                return Carbon::SUNDAY;
                break;
            default:
                break;
        }

    }

    /**
     * Retrieves the most recently created scheduled task.
     *
     * This method returns the most recently created scheduled task from the database.
     *
     * @tag srm_schdular
     */
    public function getRecentScheduledReport()
    {
        return ScheduledTask::orderBy('created_at', 'desc')->first();
    }

}
