<?php

namespace WPQT\Stage;

if (!defined('ABSPATH')) {
    exit;
}

use WPQT\Services\ServiceLocator;
use WPQT\StageHasTasksException;

if (!class_exists('WPQT\Stage\StageService')) {
    class StageService
    {
        /**
         * Creates a stage for a pipeline.
         *
         * @param int $pipelineId The ID of the pipeline.
         * @param array $args The arguments for creating the stage.
         * @return Stage The created stage.
         * @throws Exception If required fields are missing or if the stage creation fails.
         */
        public function createStage($pipelineId, $args)
        {
            global $wpdb;

            $defaults = [
                'name'        => null,
                'description' => null
            ];

            $args = wp_parse_args($args, $defaults);

            if (empty($args['name'])) {
                throw new \Exception('Required fields are missing');
            }

            $result = $wpdb->insert(TABLE_WP_QUICKTASKER_PIPELINE_STAGES, [
                'pipeline_id' => $pipelineId,
                'name'        => $args['name'],
                'description' => $args['description'],
                'created_at'  => ServiceLocator::get('TimeRepository')->getCurrentUTCTime(),
                'updated_at'  => ServiceLocator::get('TimeRepository')->getCurrentUTCTime()
            ]);

            if (false === $result) {
                throw new \Exception('Failed to create a stage');
            }

            $stageId = $wpdb->insert_id;
            $stageOrder = ServiceLocator::get('StageRepository')->getNextStageOrder($pipelineId);

            $this->addStageLocation($pipelineId, $stageId, $stageOrder);

            return ServiceLocator::get('StageRepository')->getStageById($stageId);
        }

        /**
         * Adds a stage location to the pipeline.
         *
         * @param int $pipelineId The ID of the pipeline.
         * @param int $stageId The ID of the stage.
         * @param int $stageOrder The order of the stage.
         * @return void
         */
        private function addStageLocation($pipelineId, $stageId, $stageOrder)
        {
            global $wpdb;

            $wpdb->insert(TABLE_WP_QUICKTASKER_STAGES_LOCATION, [
                'pipeline_id' => $pipelineId,
                'stage_id'    => $stageId,
                'stage_order' => $stageOrder,
                'created_at'  => ServiceLocator::get('TimeRepository')->getCurrentUTCTime(),
                'updated_at'  => ServiceLocator::get('TimeRepository')->getCurrentUTCTime()
            ]);
        }

        /**
         * Edit a stage.
         *
         * @param int $stageId The ID of the stage to edit.
         * @param array $args The arguments to update the stage.
         * @return void
         */
        public function editStage($stageId, $args)
        {
            global $wpdb;

            if (!array_key_exists('name', $args) || !array_key_exists('description', $args)) {
                throw new \Exception('Required fields are missing');
            }

            $result = $wpdb->update(TABLE_WP_QUICKTASKER_PIPELINE_STAGES, [
                'name'        => $args['name'],
                'description' => $args['description'],
                'updated_at'  => ServiceLocator::get('TimeRepository')->getCurrentUTCTime()
            ], ['id' => $stageId]);

            if (false === $result) {
                throw new \Exception('Failed to update the stage');
            }

            return ServiceLocator::get('StageRepository')->getStageById($stageId);
        }

        /**
         * Moves a stage within a pipeline.
         *
         * @param int $stageId The ID of the stage to be moved.
         * @param array $args The arguments for moving the stage.
         *                    - direction (string): The direction in which to move the stage ('left' or 'right').
         *
         * @return Stage|null The updated stage object if successful, null otherwise.
         *
         * @throws Exception If the required fields are missing, the stage is not found,
         *                   or the stage is already at the beginning/end.
         */
        public function moveStage($stageId, $args)
        {
            global $wpdb;

            if (!array_key_exists('direction', $args)) {
                throw new \Exception('Required fields are missing');
            }

            $stage = ServiceLocator::get('StageRepository')->getStageById($stageId);
            $stages = ServiceLocator::get('StageRepository')->getStagesByPipelineId($stage->pipeline_id);
            $direction = $args['direction'];

            if (!$stage) {
                throw new \Exception('Stage not found');
            }
            $currentOrder = $stage->stage_order;

            if ('left' === $direction && $currentOrder <= 0) {
                throw new \Exception('Stage is already at the beginning');
            }

            if ('right' === $direction && $currentOrder === count($stages) - 1) {
                throw new \Exception('Stage is already at the end');
            }

            $newOrder = $currentOrder + ('left' === $direction ? -1 : 1);

            $this->updateStageOrder($stageId, $stage->pipeline_id, $newOrder, $currentOrder);

            return ServiceLocator::get('StageRepository')->getStageById($stageId);
        }

        /**
         * Updates the stage order in the database.
         *
         * @param int $stageId The ID of the stage.
         * @param int $pipelineId The ID of the pipeline.
         * @param int $newOrder The new order of the stage.
         * @param int $currentOrder The current order of the stage.
         * @return int The number of rows affected by the update.
         * @throws Exception If the update fails.
         */
        private function updateStageOrder($stageId, $pipelineId, $newOrder, $currentOrder)
        {
            global $wpdb;

            $currentUtcTime = ServiceLocator::get('TimeRepository')->getCurrentUTCTime();

            if ($currentOrder < $newOrder) {
                $result = $wpdb->query(
                    $wpdb->prepare(
                        'UPDATE ' . TABLE_WP_QUICKTASKER_STAGES_LOCATION . ' SET stage_order = stage_order - 1, updated_at = %s WHERE pipeline_id = %d AND stage_order > %d AND stage_order <= %d',
                        $currentUtcTime,
                        $pipelineId,
                        $currentOrder,
                        $newOrder
                    )
                );

                if (false === $result) {
                    throw new \Exception('Failed to update stage order');
                }
            } else {
                $result = $wpdb->query(
                    $wpdb->prepare(
                        'UPDATE ' . TABLE_WP_QUICKTASKER_STAGES_LOCATION . ' SET stage_order = stage_order + 1, updated_at = %s WHERE pipeline_id = %d AND stage_order >= %d AND stage_order < %d',
                        $currentUtcTime,
                        $pipelineId,
                        $newOrder,
                        $currentOrder
                    )
                );

                if (false === $result) {
                    throw new \Exception('Failed to update stage order');
                }
            }

            $result = $wpdb->update(TABLE_WP_QUICKTASKER_STAGES_LOCATION, [
                'stage_order' => $newOrder
            ], ['stage_id' => $stageId, 'pipeline_id' => $pipelineId]);

            if (false === $result) {
                throw new \Exception('Failed to update stage order');
            }

            return $result;
        }

        /**
         * Deletes a stage from the pipeline.
         *
         * @param int $stageId The ID of the stage to delete.
         * @return Stage The deleted stage object.
         * @throws \Exception If the stage has tasks or if the deletion fails.
         */
        public function deleteStage($stageId)
        {
            global $wpdb;

            // Check if the stage has tasks
            if (count(ServiceLocator::get('TaskRepository')->getTasksByStageId($stageId)) > 0) {
                throw new StageHasTasksException('Stage has tasks. Please delete/relocate the tasks first.');
            }
            $stage = ServiceLocator::get('StageRepository')->getStageById($stageId);

            // Delete the stage from the pipeline stages table
            $result = $wpdb->delete(TABLE_WP_QUICKTASKER_PIPELINE_STAGES, ['id' => $stageId]);
            if (false === $result) {
                throw new \Exception('Failed to delete the stage.');
            }

            // Delete the stage from the stages location table
            $result2 = $wpdb->delete(TABLE_WP_QUICKTASKER_STAGES_LOCATION, ['stage_id' => $stageId]);
            if (false === $result2) {
                throw new \Exception('Failed to delete the stage.');
            }

            return $stage;
        }

        /**
         * Deletes all stages associated with a given pipeline ID.
         *
         * This function deletes all stages from the pipeline stages table and
         * their corresponding locations from the stages location table.
         *
         * @param int $pipelineId The ID of the pipeline whose stages are to be deleted.
         * @return bool True if the deletion was successful, false otherwise.
         * @throws \Exception If the deletion fails.
         */
        public function deleteStagesByPipelineId($pipelineId)
        {
            global $wpdb;

            // Delete the pipeline stages
            $result = $wpdb->delete(TABLE_WP_QUICKTASKER_PIPELINE_STAGES, [
                'pipeline_id' => $pipelineId
            ]);

            if (false === $result) {
                throw new \Exception('Failed to delete board stages');
            }

            // Delete the pipeline stages location
            $result = $wpdb->delete(TABLE_WP_QUICKTASKER_STAGES_LOCATION, [
                'pipeline_id' => $pipelineId
            ]);

            if (false === $result) {
                throw new \Exception('Failed to delete board stages location');
            }

            return true;
        }

        /**
         * Archives all tasks associated with a given stage.
         *
         * This function updates the tasks and their locations to mark them as archived.
         * It sets the `is_archived` flag to 1 and updates the `updated_at` timestamp
         * to the current UTC time.
         *
         * @param int $stageId The ID of the stage whose tasks are to be archived.
         * @return array The list of tasks that were archived.
         * @throws \Exception If the task or task location archiving fails.
         */
        public function archiveStageTasks($stageId)
        {
            global $wpdb;

            $currentUtcTime = ServiceLocator::get('TimeRepository')->getCurrentUTCTime();
            $tasksToArvhive = ServiceLocator::get('TaskRepository')->getTasksByStageId($stageId);

            $sql = '
                UPDATE ' . TABLE_WP_QUICKTASKER_TASKS . ' AS a
                INNER JOIN ' . TABLE_WP_QUICKTASKER_TASKS_LOCATION . ' AS b
                ON a.id = b.task_id
                SET a.is_archived = 1, a.updated_at = %s
                WHERE b.stage_id = %d
            ';

            $result = $wpdb->query($wpdb->prepare($sql, $currentUtcTime, $stageId));

            if (false === $result) {
                throw new \Exception('Failed to archive tasks');
            }

            $result2 = $wpdb->update(
                TABLE_WP_QUICKTASKER_TASKS_LOCATION,
                [
                    'is_archived' => 1,
                    'updated_at'  => $currentUtcTime
                ],
                ['stage_id' => $stageId],
                ['%d', '%s'], // Format for data: integer and string
                ['%d']        // Format for where clause: integer
            );

            if (false === $result2) {
                throw new \Exception('Failed to archive task locations');
            }

            return $tasksToArvhive;
        }

        /**
         * Validates that a stage and its pipeline exist.
         *
         * @param int $stageId The stage ID to validate.
         * @param int $pipelineId The pipeline ID to validate.
         * @throws \WPQT\StageMissingException If stage doesn't exist.
         * @throws \WPQT\PipelineMissingException If pipeline doesn't exist.
         * @return void
         */
        public function validateStageAndPipeline($stageId, $pipelineId): void
        {
            $stageRepo = ServiceLocator::get('StageRepository');
            $pipelineRepo = ServiceLocator::get('PipelineRepository');

            $stage = $stageRepo->getStageById($stageId);
            $pipelineExists = $pipelineRepo->checkIfPipelineExists($pipelineId);

            if ($stage && ((int) $stage->pipeline_id !== (int) $pipelineId)) {
                throw new \Exception('Stage with id ' . $stageId . ' does not belong to pipeline with id ' . $pipelineId);
            }

            if ($pipelineExists && !$stage) {
                throw new \WPQT\StageMissingException('No stage found with id ' . $stageId);
            }
            if (false === $pipelineExists) {
                throw new \WPQT\PipelineMissingException('No pipeline found with id ' . $pipelineId);
            }
        }
    }
}
