<?php

namespace HelpGent\App\Http\Controllers;

defined( 'ABSPATH' ) || exit;

use WP_REST_Request;
use HelpGent\App\DTO\UserResponseReadDTO;
use HelpGent\App\Http\Controllers\Controller;
use HelpGent\App\Models\ElementAnswers;
use HelpGent\App\Models\Form;
use HelpGent\App\Models\Message;
use HelpGent\App\Models\HelpGentUser;
use HelpGent\App\Providers\EmailNotificationProvider;
use HelpGent\App\Repositories\AddressRepository;
use HelpGent\App\Repositories\ContactInfoRepository;
use HelpGent\App\Repositories\ElementAnswerRepository;
use HelpGent\App\Repositories\OpenEndedTextInnerRepository;
use HelpGent\App\Repositories\QuizRepository;
use HelpGent\App\Repositories\ResponseRepository;
use HelpGent\App\Repositories\FormRepository;
use HelpGent\App\Utils\Repository;
use HelpGent\WpMVC\RequestValidator\Validator;
use HelpGent\WpMVC\Routing\Response;
use HelpGent\WpMVC\Exceptions\Exception;
use HelpGent\App\DTO\ContactInfoDTO;
use HelpGent\App\DTO\ResponseDTO;
use HelpGent\App\DTO\UserDTO;
use HelpGent\App\EnumeratedList\UserType;
use stdClass;

class ResponseController extends Controller {
    public ResponseRepository $repository;

    public ElementAnswerRepository $element_answer_repository;

    public ContactInfoRepository $contact_info_repository;

    public OpenEndedTextInnerRepository $open_ended_text_inner_repository;

    public AddressRepository $address_repository;

    public FormRepository $form_repository;

    public function __construct(
        ResponseRepository $repository,
        ElementAnswerRepository $element_answer_repository,
        ContactInfoRepository $contact_info_repository,
        OpenEndedTextInnerRepository $open_ended_text_inner_repository,
        AddressRepository $address_repository,
        FormRepository $form_repository
    ) {
        $this->repository                       = $repository;
        $this->element_answer_repository        = $element_answer_repository;
        $this->contact_info_repository          = $contact_info_repository;
        $this->open_ended_text_inner_repository = $open_ended_text_inner_repository;
        $this->address_repository               = $address_repository;
        $this->form_repository                  = $form_repository;
    }

    protected function maybe_delete_empty_response( int $response_id, int $form_id, ?string $response_token = null ): void {
        if ( ! $response_id || $this->repository->has_content( $response_id ) ) {
            return;
        }

        if ( ! empty( $response_token ) ) {
            $this->form_repository->delete_meta( $form_id, $response_token );
        }

        $this->repository->delete( $response_id );
    }

    protected function get_exception_messages( \Exception $exception ): array {
        if ( method_exists( $exception, 'get_messages' ) ) {
            $messages = $exception->get_messages();

            if ( is_array( $messages ) && ! empty( $messages ) ) {
                return $messages;
            }
        }

        $message = trim( $exception->getMessage() );

        if ( '' === $message ) {
            $message = esc_html__( 'Something went wrong.', 'helpgent' );
        }

        return [ $message ];
    }

    public function index( Validator $validator, WP_REST_Request $wp_rest_request ) {
        $validator->validate(
            [
                'per_page' => 'numeric',
                'page'     => 'numeric',
                'order_by' => 'required|string|accepted:read,unread,latest,oldest',
                'search'   => 'string'
            ]
        );

        $user = helpgent_get_current_user();

        $dto = ( new UserResponseReadDTO() )
            ->set_created_by( $user->get_id() )
            ->set_per_page( intval( $wp_rest_request->get_param( 'per_page' ) ) )
            ->set_page( intval( $wp_rest_request->get_param( 'page' ) ) )
            ->set_order_by( $wp_rest_request->get_param( 'order_by' ) )
            ->set_search( $wp_rest_request->get_param( 'search' ) );

        return Response::send( $this->repository->user_get( $dto ) );
    }

    public function get_agent( Validator $validator, WP_REST_Request $wp_rest_request ) {
        $validator->validate(
            [
                'id' => 'required|numeric'
            ]
        );

        $current_user = helpgent_get_current_user();

        if ( ! $current_user ) {
            throw new Exception( esc_html__( "Agent not found", "helpgent" ) );
        }

        $message = Message::query( "message" )->where( "response_id", $wp_rest_request->get_param( "id" ) )
        ->where( "message.created_by", "!=", $current_user->get_id() )
        ->order_by_desc( "id" )->first();

        if ( ! $message ) {
            throw new Exception( esc_html__( "Agent not found", "helpgent" ) );
        }

        $user = HelpGentUser::query()->where( "id", $message->created_by )->first();

        if ( ! $user ) {
            throw new Exception( esc_html__( "Agent not found", "helpgent" ) );
        }

        $agent = [
            "name"       => $user->name,
            "avatar_url" => get_avatar_url( $user->email )
        ];

        return Response::send(
            [
                'agent' => $agent
            ]
        );
    }

    public function get_form_info( Validator $validator, WP_REST_Request $wp_rest_request ) {
        $validator->validate(
            [
                'id' => 'required|numeric'
            ]
        );

        $form = \HelpGent\App\Models\Response::query( "response" )->select( "form.id", "form.title", "form.content" )
        ->where( "response.id", $wp_rest_request->get_param( 'id' ) )
        ->left_join( Form::get_table_name() . ' as form', 'response.form_id', 'form.id' )->first();

        if ( ! $form ) {
            throw new Exception( esc_html__( "Form not found", "helpgent" ) );
        }

        $form = Repository::bind_form_featured_image( $form );
        unset( $form->content );

        return Response::send(
            [
                'form' => helpgent_migrate_old_form( $form )
            ]
        );
    }

    public function store( Validator $validator, WP_REST_Request $request ) {
        $validator->validate(
            [
                'form_id'           => 'required|numeric',
                'response_token'    => 'string',
                'data'              => 'required|array',
                'attachment_tokens' => 'required|array',
                'user'              => 'array',
            ]
        );

        $this->validate_quiz_timer_end( $request );

        $form = $this->get_response_form( $request );

        if ( function_exists( "helpgent_pro" ) ) {
            $is_active_incompleted_data = intval( helpgent_get_nested_value( 'global.is_active_save_incomplete_data', $form->content ) );

            if ( $is_active_incompleted_data ) {
                throw new Exception( esc_html__( 'Sending complete data in a single request is not allowed', 'helpgent' ), 500 );
            }
        }

        $response_token = $request->get_param( 'response_token' );

        $validator->wp_rest_request = $request;
        $user                      = null;
        $response_id               = 0;
        $response_update_dto       = null;
        $should_cleanup_on_failure = false;

        if ( empty( $response_token ) ) {
            $user = $this->insert_user( $validator, $request );

            $dto = ( new ResponseDTO() )
                ->set_form_id( $form->id )
                ->set_created_by( $user['user_id'] )
                ->set_is_verified( 0 )
                ->set_ip( helpgent_get_user_ip_address() );

            $response_id = $this->repository->create( $dto );

            $response_token = $this->repository->get_token( $form->id, $response_id );
            $request->set_param( 'response_token', $response_token );
            $should_cleanup_on_failure = true;

            $response_update_dto = ( new ResponseDTO() )
                ->set_id( $response_id )
                ->set_is_completed( 1 )
                ->set_ip( helpgent_get_user_ip_address() )
                ->set_submitted_at( helpgent_now()->to_date_time_string() );
        } else {
            $response = helpgent_get_response_by_token( $response_token, $form->id );

            if ( ! $response ) {
                throw new Exception( esc_html__( "Response not found.", "helpgent" ) );
            }

            $response_update_dto = ( new ResponseDTO() )
                ->set_id( $response->id )
                ->set_is_completed( 1 )
                ->set_ip( helpgent_get_user_ip_address() )
                ->set_submitted_at( helpgent_now()->to_date_time_string() );


            $updated_user_id = $this->update_response_user( $response, $validator, $request );

            if ( null !== $updated_user_id ) {
                $response_update_dto->set_created_by( $updated_user_id );
            }

            $response_id = $response->id;
            $should_cleanup_on_failure = ! $this->repository->has_content( $response_id );
        }

        try {
            $this->insert_data( $validator, $request, $response_id, $form );
        } catch ( \Exception $exception ) {
            if ( $should_cleanup_on_failure ) {
                $this->maybe_delete_empty_response(
                    $response_id,
                    $form->id,
                    $response_token
                );
            }

            throw $exception;
        }

        if ( $response_update_dto ) {
            $this->repository->update( $response_update_dto );
        }

        $this->repository->update_user_activity( $response_id );

        if ( ! empty( $user['request'] ) && $user['request'] instanceof WP_REST_Request ) {
            $response_contact_info = $this->contact_info_repository->get_response_contact_info( $response_id );
            $user_request          = $user['request'];

            if ( ! $response_contact_info ) {
                $contact_dto = ( new ContactInfoDTO() )->set_form_id( $form->id )
                ->set_response_id( $response_id )
                ->set_name( $user_request->get_param( 'name' ) )
                ->set_email( $user_request->get_param( 'email' ) )
                ->set_mobile_number( $user_request->get_param( 'number' ) )
                ->set_company( $user_request->get_param( 'company' ) )
                ->set_form_data( ['created_by' => $form->created_by ] )
                ->set_is_lead( 1 );

                $this->contact_info_repository->create( $contact_dto );
            } else {
                $contact_dto = ( new ContactInfoDTO() )->set_id( $response_contact_info->id )
                ->set_name( $user_request->get_param( 'name' ) )
                ->set_email( $user_request->get_param( 'email' ) )
                ->set_mobile_number( $user_request->get_param( 'number' ) )
                ->set_company( $user_request->get_param( 'company' ) );

                $this->contact_info_repository->update( $contact_dto );
            }
        }

        $response = $this->repository->get_by_id( $response_id );

        /**
         * Temporary disable email notification if response created by unknown user.
         * TODO
         */
        if ( 0 === intval( $response->created_by ) ) {
            remove_action( 'helpgent_after_submit_form', [helpgent_singleton( EmailNotificationProvider::class ), 'send_new_response_notification'] );
        }

        do_action( 'helpgent_after_submit_form', $response, $form, $request );

        /**
         * @var QuizRepository $quiz_repository
         */
        $quiz_repository = helpgent_singleton( QuizRepository::class );

        return Response::send(
            [
                'response_token' => $response_token,
                'quiz'           => $quiz_repository->sync_response( $response_id ),
            ],
            201
        );
    }

    public function store_single( Validator $validator, WP_REST_Request $wp_rest_request ) {
        $validator->validate(
            [
                'form_id'           => 'required|numeric',
                'element_id'        => 'required|string',
                'response_token'    => 'required|string',
                'attachment_tokens' => 'required|array',
                'submit'            => 'accepted:1',
            ]
        );

        $this->validate_quiz_timer_end( $wp_rest_request );

        $form     = $this->get_response_form( $wp_rest_request );
        $response = helpgent_get_response_by_token( $wp_rest_request->get_param( 'response_token' ), $form->id );

        if ( ! $response ) {
            throw new Exception( esc_html__( "Response not found.", "helpgent" ) );
        }

        if ( '1' === $response->is_completed ) {
            throw new Exception( esc_html__( "Response Already Completed.", "helpgent" ) );
        }

        if ( $form->id != $response->form_id ) {
            throw new Exception( esc_html__( "Sorry, Oops, something went wrong. Please try again.", "helpgent" ) );
        }
        $should_cleanup_on_failure = ! $this->repository->has_content( $response->id );

        try {
            /**
             * Validate element
             */
            $element = $this->get_current_element( $wp_rest_request, $form );

            if ( in_array( $element['element_type'], helpgent_allowed_elements_on_submission(), true ) ) {
                /**
                 * Get input field handler by field type and validate input.
                 */
                $element_handler = helpgent_element_handler( $element['element_type'] );

                $element_handler->validate( $wp_rest_request, $validator, $element );

                /**
                 * Storing response response.
                 */
                $save_answer = $element_handler->save_answer( $wp_rest_request, $element, $response->id, $form );

                if ( $save_answer ) {
                    $this->repository->update_submitted_at( $response->id );
                }
            }

            $attachment_tokens = $wp_rest_request->get_param( 'attachment_tokens' );

            if ( ! empty( $attachment_tokens ) ) {
                $this->repository->add_attachments( $response->id, $attachment_tokens );
            }

            $this->submit_form( $response, $form, $wp_rest_request );
        } catch ( \Exception $exception ) {
            if ( $should_cleanup_on_failure ) {
                $this->maybe_delete_empty_response(
                    $response->id,
                    $form->id,
                    $wp_rest_request->get_param( 'response_token' )
                );
            }

            throw $exception;
        }

        $this->repository->update_user_activity( intval( $response->id ) );

        /**
         * @var QuizRepository $quiz_repository
         */
        $quiz_repository = helpgent_singleton( QuizRepository::class );

        return Response::send(
            [
                'response_token' => $wp_rest_request->get_param( 'response_token' ),
                'quiz'           => $quiz_repository->sync_response( intval( $response->id ) ),
            ],
            201
        );
    }

    private function submit_form( stdClass $response, stdClass $form, WP_REST_Request $wp_rest_request ) {
        if ( ! $wp_rest_request->has_param( 'submit' ) ) {
            return;
        }

        $this->repository->mark_as_completed( $response->id );

        do_action( 'helpgent_after_submit_form', $response, $form, $wp_rest_request );
    }

    private function get_current_element( WP_REST_Request $wp_rest_request, stdClass $form ) {
        $elements = $form->content['elements'];

        $element_index = array_search( $wp_rest_request->get_param( 'element_id' ), array_column( $elements, 'id' ) );

        if ( ! is_int( $element_index ) ) {
            throw new Exception( esc_html__( "Element not found", "helpgent" ), 500 );
        }

        return $elements[$element_index];
    }

    private function validate_quiz_timer_end( WP_REST_Request $request ) {
        /**
         * @var QuizRepository $quiz_repository
         */
        $quiz_repository = helpgent_singleton( QuizRepository::class );
        $quiz_repository->assert_response_not_expired(
            intval( $request->get_param( 'form_id' ) ),
            $request->get_param( 'response_token' ),
            $request->get_param( 'quiz_timer_end' )
        );
    }

    public function get_response_form( WP_REST_Request $request ) {
        $form_id = intval( $request->get_param( 'form_id' ) );

        $columns = ['form.id', 'form.title', 'form.status', 'form.content', 'form.created_by'];

        $form_query = Form::query( 'form' )->select( $columns )->where( 'form.id', $form_id )->where( 'form.status', 'publish' )->group_by( $columns );

        do_action( 'helpgent_response_store_from_query', $form_query );

        $join_form_meta = apply_filters( 'helpgent_response_store_from_query_join_meta', false );

        if ( $join_form_meta ) {
            $form_query->left_join( "helpgent_form_meta as form_meta", "form_meta.form_id", "=", "form.id" );
        }

        $form = $form_query->first();

        if ( ! $form ) {
            throw new Exception( esc_html__( "Form not found.", "helpgent" ) );
        }

        return helpgent_migrate_old_form( $form, false );
    }

    public function get_user_validation_rules() {
        return [
            'name'    => 'string|max:255',
            'email'   => 'required|string|email|max:255',
            'number'  => 'string|max:255',
            'company' => 'string|max:255',
            'form_id' => 'required|numeric'
        ];
    }

    protected function insert_user( Validator $validator, WP_REST_Request $request ) {
        $user = $request->get_param( 'user' );

        /**
         * @var UserController $user_controller
         */
        $user_controller = helpgent_singleton( UserController::class );

        if ( empty( $user ) ) {
            $dto = ( new UserDTO() )
                ->set_user_type( UserType::GUEST )
                ->set_token( helpgent_generate_token() )
                ->set_token_expires_at( helpgent_now()->add_days( 30 ) );

            return [
                'user_id' => $user_controller->repository->create( $dto ),
                'request' => $request
            ];
        }

        $user_request = clone $request;
        $user_request->set_body_params( $user );
        $validator->wp_rest_request = $user_request;

        $validator->validate( $this->get_user_validation_rules() );

        if ( $validator->is_fail() ) {
            $error = new Exception( '', 422 );

            $error->set_messages(
                [
                    'user' => $validator->errors
                ]
            );

            throw $error;
        }

        $email = $user_request->get_param( 'email' );

        $user = $user_controller->repository->get_by_email( $email, 'id' );

        if ( $user ) {
            return [
                'user_id' => $user->id,
                'request' => $user_request
            ];
        }

        $dto = ( new UserDTO() )
            ->set_user_type( UserType::USER )
            ->set_email( $email )
            ->set_name( $user_request->get_param( 'name' ) )
            ->set_number( $user_request->get_param( 'number' ) )
            ->set_company( $user_request->get_param( 'company' ) )
            ->set_token( helpgent_generate_token() )
            ->set_token_expires_at( helpgent_now()->add_days( 30 ) );

        return [
            'user_id' => $user_controller->repository->create( $dto ),
            'request' => $user_request
        ];
    }

    public function update_response_user( stdClass $response, Validator $validator, WP_REST_Request $request ) {
        $user = $request->get_param( 'user' );

        if ( empty( $user ) ) {
            return null;
        }

        $user_request = clone $request;
        $user_request->set_body_params( $user );
        $validator->wp_rest_request = $user_request;

        $validator->validate( $this->get_user_validation_rules() );

        if ( $validator->is_fail() ) {
            $error = new Exception( '', 422 );

            $error->set_messages(
                [
                    'user' => $validator->errors
                ]
            );

            throw $error;
        }

        /**
         * @var UserController $user_controller
         */
        $user_controller = helpgent_singleton( UserController::class );

        $email = $user_request->get_param( 'email' );
        $user  = $user_controller->repository->get_by_email( $email, 'id' );

        if ( $user ) {
            // Switch user
            return $user_controller->repository->switch_user( $response->created_by, $user->id, true );
        }

        $dto = ( new UserDTO() )
            ->set_id( $response->created_by )
            ->set_user_type( UserType::USER )
            ->set_email( $email )
            ->set_name( $user_request->get_param( 'name' ) )
            ->set_number( $user_request->get_param( 'number' ) )
            ->set_company( $user_request->get_param( 'company' ) );

        $user_controller->repository->update( $dto );

        return null;
    }

    public function insert_data( Validator $validator, WP_REST_Request $wp_rest_request, int $response_id, stdClass $form ) {
        $data = $wp_rest_request->get_param( 'data' );

        $elements     = $form->content['elements'];
        $elements_ids = array_column( $elements, 'id' );

        $allowed_elements = helpgent_allowed_elements_on_submission();

        $request                    = new WP_REST_Request( 'POST', '/' );
        $validator->wp_rest_request = $request;
        $errors                     = [];
        $items                      = [];
        $nested_elements            = [];

        /**
         * Validate all element answers one by one
         */
        foreach ( $data as $key => $values ) {
            $element_id  = $values['element_id'];
            $element_key = array_search( $element_id, $elements_ids );

            /**
             * Skip if element not found in db elements list
             */
            if ( ! is_int( $element_key ) ) {
                unset( $data[$key] );
                continue;
            }

            $element      = $elements[ $element_key ];
            $element_type = $element['element_type'];

            /**
             * Skip if element is not allowed to submit
             */
            if ( ! in_array( $element_type, $allowed_elements, true ) ) {
                unset( $data[$key] );
                continue;
            }

            $values['form_id'] = $form->id;

            $request->set_body_params( $values );

            try {
                $element_handler = helpgent_element_handler( $element_type );

                if ( $element_handler->skip_full_submission ) {
                    continue;
                }

                $element_handler->validate( $request, $validator, $element );

                if ( ! $element_handler->has_nested_answer ) {
                    $element_value = $values[ $element_handler::get_key() ] ?? null;

                    if ( 0 !== $element_value && empty( $element_value ) ) {
                        continue;
                    }
                }

                $dto     = $element_handler->get_answer_dto( $values, $element, $response_id, $form->id );
                $items[] = $dto;

                /**
                 * Creating separate array to submit nested data
                 */
                if ( $element_handler->has_nested_answer ) {
                    $values['element_type']         = $element_type;
                    $values['element']              = $element;
                    $nested_elements[ $element_id ] = $values;
                }
            } catch ( \Exception $exception ) {
                $errors[ $element_id ] = $this->get_exception_messages( $exception );
            }
        }

        if ( ! empty( $errors ) ) {
            $errpr = new Exception( '', 422 );

            $errpr->set_messages( $errors );

            throw $errpr;
        }

        /**
         * Insert elements
         */
        $this->element_answer_repository->creates( ...$items );

        if ( ! empty( $nested_elements ) ) {
            $element_ids = array_keys( $nested_elements );

            /**
             * Get inserted elements id to submit nested data
             */
            $parent_answers = ElementAnswers::query()->select( 'id', 'element_id' )->where( 'response_id', $response_id )->where_in( 'element_id', $element_ids )->get();

            foreach ( $parent_answers as $parent_answer ) {
                if ( ! isset( $nested_elements[ $parent_answer->element_id ] ) ) {
                    continue;
                }

                $nested_data     = $nested_elements[ $parent_answer->element_id ];
                $element_handler = helpgent_element_handler( $nested_data[ 'element_type' ] );

                $element_handler->save_nested_answer( $parent_answer->id, $nested_data, $form );
            }
        }

        $attachment_tokens = $wp_rest_request->get_param( 'attachment_tokens' );

        if ( ! empty( $attachment_tokens ) ) {
            $this->repository->add_attachments( $response_id, $attachment_tokens );
        }
    }

    public function generate_token( Validator $validator, WP_REST_Request $wp_rest_request ) {
        $validator->validate(
            [
                'form_id' => 'required|numeric'
            ]
        );

        $form = $this->form_repository->get_by_id_publish( $wp_rest_request->get_param( 'form_id' ) );

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ) );
        }

        /**
         * @var UserController $user_controller
         */
        $user_controller = helpgent_singleton( UserController::class );

        $user_dto = ( new UserDTO() )
            ->set_user_type( UserType::GUEST )
            ->set_email( '' )
            ->set_token( helpgent_generate_token() )
            ->set_token_expires_at( helpgent_now()->add_days( 30 ) );

        $user_id = $user_controller->repository->create( $user_dto );

        $response_dto = ( new ResponseDTO )
            ->set_created_by( $user_id )
            ->set_form_id( $form->id )
            ->set_ip( helpgent_get_user_ip_address() );

        return Response::send(
            [ 'response_token' => $this->repository->create_and_get_token( $response_dto ) ]
        );
    }
}
