<?php

namespace App\Http\Controllers\USSD;

use App\DataTables\USSD\UssdResponseReportDatatable;
use App\DataTables\USSD\USSDServiceDatatable;
use App\DataTables\UssdCollectionResponseDatatable;
use App\Http\Controllers\Controller;
use App\PaymentResponse;
use App\UssdResponse;
use App\UssdService;
use App\UssdCollection;
use App\UssdServiceType;
use DB;
use Illuminate\Http\Request;

class USSDServiceController extends Controller
{
    /**
     * Display a listing of the resource.
     * @param USSDServiceDatatable $table
     * @return \Illuminate\Http\JsonResponse|\Illuminate\View\View
     */
    public function index(USSDServiceDatatable $table)
    {
        return $table->render("ussd/service/index");
    }

    /**
     * Show the form for creating a new resource.
     * If it is a post request, store the data.
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
     * @throws \Exception
     */
    public function create()
    {
        if (strtoupper(\request()->isMethod('post'))) {
            return $this->storeUssdService();
        }

        return view('ussd.service.create')->with(
            [
                'serviceTypes' => UssdServiceType::all(),
                'code' => UssdService::generateUssdCode()
            ]
        );
    }

    /**
     * Store Service data
     * @return \Illuminate\Http\RedirectResponse
     * @throws \Exception
     */
    private function storeUssdService()
    {
        $this->validate(\request(),
            [
                'name' => 'required|string|max:250|unique:ussd_services,name',
                'code' => 'required|string|max:250|unique:ussd_services,code',
                'description' => 'string',
                'keyword' => 'sometimes|string',
                'ussd_service_type_id' => 'required|integer'
            ]
        );
        //get the payment details for the user - username and password
        //merge to the incoming request
        \request()->merge(
            [
                'sms_username' => auth()->user()->username,
                'sms_password' => auth()->user()->pass
            ]
        );

        DB::beginTransaction();
        //create new ussd service with request data
        $ussdService = UssdService::create(\request()->all());

        //send service code to the ussd table of Npontu
        $this->publishService($ussdService);
        DB::commit();

        session()->flash('success', 'USSD Service successfully created and published.');
        return redirect()->route('service.ussd.subscription.question', $ussdService->id);
    }

    /**
     * Extract ussd service code extension. ie *899*45# returns 45.
     * @param $code
     * @return mixed
     */
    private function extractExtensionFromServiceCode($code)
    {
        //split the code on #, ie *899*45# will become *899*45
        $extensionPart = explode('#', $code);
        //split the extension part on * and the extension ie 45 will be in the 2nd index
        return explode('*', $extensionPart[0])[2];
    }

    /**
     * Publish a service to the live ussd table. Activates the service to be accessible by clients via their mobile
     * phones
     * @param UssdService $service
     * @return bool|\mysqli_result
     */
    private function publishService(UssdService $service)
    {
        //prepare the data for storage
        $serviceName = $service->name;
        $extension = $this->extractExtensionFromServiceCode($service->code);
        //establish connection
        $connection = $this->ussdLiveDBConnection();
        //insert data into the db
        return mysqli_query($connection, "INSERT INTO services (extension,service,dynamic) VALUES ('$extension','$serviceName','yes')");
    }

    /**
     *Unpublish a service from the live ussd table. De-Activates the service to be accessible by clients via their mobile
     * @param UssdService $service
     * @return bool|\mysqli_result
     */
    private function unpublishService(UssdService $service)
    {
        //prepare the data for storage
        $serviceName = $service->name;
        $extension = $this->extractExtensionFromServiceCode($service->code);

        //establish connection
        $connection = $this->ussdLiveDBConnection();
        //delete service data from the db
        return mysqli_query($connection, "DELETE FROM services WHERE `service` = '$serviceName' AND `extension` = '$extension'");
    }

    /**
     * Establishes connection to the live database where the service can be published to for the ussd to take effect
     * @return false|\mysqli
     */
    private function ussdLiveDBConnection()
    {
        return mysqli_connect('95.216.10.33', "stealBars_of_Jerico", "H3dFzp2DSa9S@9i", 'ussd', '3327');
    }

    /**
     * Handles the payment status update when the ussd service is used via mobile phone
     * Note that the payment initiation is done by the api that handles the dynamic ussd logic
     * @param Request $request
     */
    public function paymentCallback(Request $request)
    {
        //request comes with the following message
        //transaction_id
        //status
        //responseMessage
        $transactionId = $request->get('transaction_id');
        $status = $request->get('status');
        $responseMessage = $request->get('responseMessage');

        //note that the transaction id will come in the format ie 12_1_syx.
        //12 is the service code extension, ie *899*12#
        //1 is the amount
        //To store these details into the Payment Response table, we nee to form the full code with the extension and
        $data = explode('_', $transactionId);

        PaymentResponse::query()->create(
            [
                'code' => '*899*' . $data[0] . '#',
                'amount' => $data[1],
                'transaction_id' => $transactionId,
                'status' => $status . '|' . $responseMessage
            ]
        );
    }


    /**
     * Display the specified resource.
     *
     * @param int $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $service = UssdService::find($id);
        return view('ussd.service.show')->withService($service);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param int $id
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
     * @throws \Exception
     */
    public function edit($id)
    {
        $service = UssdService::find($id);

        if (strtoupper(\request()->isMethod('post'))) {
            return $this->updateUssdService($service);
        }

        return view('ussd.service.edit')->with(
            [
                'serviceTypes' => UssdServiceType::all(),
                'service' => $service
            ]
        );
    }

    /**
     * Update ussd service details
     * If old service type ie vote accepted sms amounts and the new type ie survey does not,
     * update the questions column to unset all amounts and sms messages
     * @param UssdService $service
     * @return \Illuminate\Http\RedirectResponse
     * @throws \Exception
     */
    public function updateUssdService(UssdService $service)
    {
        $this->validate(\request(),
            [
                'name' => 'required|string|max:250|unique:ussd_services,name,' . $service->id,
                'description' => 'string',
                'keyword' => 'sometimes|string',
                'ussd_service_type_id' => 'required|integer'
            ]
        );

        DB::beginTransaction();

        //find the new service type
        $newServiceType = UssdServiceType::find(\request()->get('ussd_service_type_id'));
        //find the old service type
        $oldServiceType = $service->ussd_service_type;

        //check if previous service is similar to the new service.
        //if it is not, check if the old service accepts sms amount.
        if ($newServiceType != $oldServiceType) {
            //check if old service type accepts sms amount and the new service does not accept sms amount,
            if ($oldServiceType->acceptsSmsAmount() && !$newServiceType->acceptsSmsAmount()) {
                // update all existing records with sms amounts attached to them
                $service->ussd_question->each(function ($question) {
                    //unset the amount and sms message
                    $question->update(['amount' => '0.00', 'sms_message' => '']);

                    //if the question has multiple choice answers, unset the amount and sms message as well
                    if ($question->isMultipleChoice()) {
                        $question->ussd_answer->each(function ($answer) {
                            $answer->update(['amount' => '0.00', 'sms_message' => '']);
                        });
                    }
                });
            }
        }

        $service->update(\request()->all());

        DB::commit();

        session()->flash('success', 'USSD Service successfully updated.');
        return redirect()->route('service.show', $service->id);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param int $id
     * @return \Illuminate\Http\Response
     * @throws \Throwable
     */
    public function destroy($id)
    {
        $service = UssdService::find($id);

        return DB::transaction(function () use ($service) {

            //delete the ussd answers
            foreach ($service->ussd_question as $question):
                $question->ussd_answer()->delete();
            endforeach;

            //delete the ussd questions
            $service->ussd_question()->delete();

            //unpublish ussd from the live ussd db
            $this->unpublishService($service);

            //delete the ussd service
            $service->delete();

            session()->flash("success", "Service successfully deleted and unpublished.");
            return redirect()->back();
        });

    }

    /**
     * Redirects user to home page and displays success message for completing creating a ussd service
     * @param $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function done($id)
    {
        $service = UssdService::find($id);

        session()->flash("success", "$service->name successfully processed.");
        return redirect()->route('service.index');
    }

    /**
     * Displays dashboard report for a ussd service
     * @param UssdService $service
     * @return mixed
     */
    public function reportDashboard(UssdService $service)
    {
        return view('ussd.service.report_dashboard')->with(
            [
                'service' => $service,
                'questionData' => $this->generateQuestionAnswerData($service),
                'totalResponses' => $this->totalResponsesCount($service),
                'totalAmount' => $this->totalPaymentAmount($service),
                'completeResponses' => $this->totalCompleteResponses($service),
                'inCompleteResponses' => $this->totalIncompleteResponses($service),
            ]
        );
    }

    public function reportCollectionsDashboard(UssdService $service)
    {
        $questionsData = [];

        $offering = UssdCollection::query()->where('category', "Offering")->where("serviceID",$service->id)->count();
        $pledge = UssdCollection::query()->where('category', "Pledge")->where("serviceID",$service->id)->count();
        $tithe = UssdCollection::query()->where('category', "Tithe")->where("serviceID",$service->id)->count();

        array_push($questionsData, ["Offering", $offering]);
        array_push($questionsData, ["Pledge", $pledge]);
        array_push($questionsData, ["Tithe", $tithe]);

        $totalResponses = UssdCollection::query()->where("serviceID",$service->id)->count();
        $totalAmount = UssdCollection::query()->where("serviceID",$service->id)->where("status","Paid")->sum("amount");
        $completeResponses = UssdCollection::query()->where("serviceID",$service->id)->where("status","Paid")->count();
        $inCompleteResponses = UssdCollection::query()->where("serviceID",$service->id)->where("status","Pending")->count();

        return view('ussd.service.collections_report_dashboard')->with(
            [
                'service' => $service,
                'questionData' => $questionsData,
                'totalResponses' => $totalResponses,
                'totalAmount' => $totalAmount,
                'completeResponses' => $completeResponses,
                'inCompleteResponses' => $inCompleteResponses,
            ]
        );
    }

    /**
     * @param UssdService $service
     * @return int
     */
    private function totalResponsesCount(UssdService $service)
    {
        //get the total no of people who responded to the ussd service;  where code = service code
        return UssdResponse::totalResponses($service)->count();
    }

    /**
     * Gets all the ussd questions for the service and the count of responses
     * @param UssdService $service
     * @return array
     */
    private function generateQuestionAnswerData(UssdService $service)
    {
        //holds the questions and the response counts
        //format = [
        //     ['this is the question', 'this is the count']
        //      ]
        //
        $questionsData = [];

        $service->ussd_question->each(function ($ussd_question) use (&$questionsData) {
            //create an array to hold the data
            array_push($questionsData, [$ussd_question->question, $ussd_question->ussd_responses->count()]
            );
        });

        return $questionsData;
    }

    /**
     * get the sum of the amounts paid by users when they responded to the ussd
     * @param UssdService $service
     * @return mixed
     */
    private function totalPaymentAmount(UssdService $service)
    {
        //get the sum of the amounts paid by users when they responded to the ussd
        $payments = PaymentResponse::query()->where('code', $service->code)->get();
        //get the sum of the amounts
        return $payments->sum('amount');
    }

    /**
     * Get the number of users that could not complete the ussd questions
     * @param UssdService $service
     * @return mixed
     */
    private function totalCompleteResponses(UssdService $service)
    {
        $responsesAndCount = $this->generateSequenceNumberResponseAndCount($service);
        $completedResponses = $this->filterResponses($responsesAndCount, $service, true);
        return $completedResponses->count();
    }


    /**
     * Get the number of users that could not complete the ussd questions
     * @param UssdService $service
     * @return mixed
     */
    private function totalIncompleteResponses(UssdService $service)
    {
        $responsesAndCount = $this->generateSequenceNumberResponseAndCount($service);
        $inCompletedResponses = $this->filterResponses($responsesAndCount, $service, false);
        return $inCompletedResponses->count();
    }

    /**
     * Gets all responses and returns the number each user dialed the service or answered the questions
     * @param UssdService $service
     * @return array
     */
    private function generateSequenceNumberResponseAndCount(UssdService $service)
    {
        //get the unique sequence no of the respondents for this service - returns
        //[ 0 => "123456", 1 => "123457"]
        $ussdResponses = UssdResponse::totalResponses($service);

        //for each sequence number, get the total number of times they have responded to a service
        $responsesAndCount = []; //final format = [['sequence_no' => 123456, 'count' =>  5], ['sequence_no' => 13457, 'count' =>  3]]
        $ussdResponses->each(function ($sequence_no) use (&$responsesAndCount, $service) {
            //get the number of responses a sequence no has made for this ussd service
            $countOfResponses = UssdResponse::query()->where(['sequence_no' => $sequence_no, 'code' => $service->code])->count();
            array_push($responsesAndCount, ['sequence_no' => $sequence_no, 'count' => $countOfResponses]);
        });

        return $responsesAndCount;
    }

    /**
     * @param array $responsesAndCount
     * @param UssdService $service
     * @param bool $isComplete
     * @return \Illuminate\Support\Collection
     */
    private function filterResponses(array $responsesAndCount, UssdService $service, $isComplete = true)
    {
        //for each sequence no and their count of interaction, check if the interaction is equal to the total number
        //of questions for the service, return the count for those equal, it means they completed the service
        //for those less than, return the count, it means they have not completed the service
        return Collect($responsesAndCount)->filter(function ($responseAndCount) use ($service, $isComplete) {
            $responseCount = $responseAndCount['count'];
            $questionCount = $service->ussd_question->count();

            return $isComplete ? $responseCount == $questionCount : $responseCount < $questionCount;
        });
    }

    /**
     * Displays response report for a ussd service
     * @param UssdResponseReportDatatable $table
     * @param UssdService $service
     * @return mixed
     */
    public function ussdResponseReport(UssdResponseReportDatatable $table, UssdService $service)
    {
        return $table->render("ussd.service.response_report", ['service' => $service]);
    }

    public function ussdCollectionResponseReport(UssdCollectionResponseDatatable $table, UssdService $service)
    {
        return $table->render("ussd.service.collection_response_report", ['service' => $service]);
    }
}
