<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\View\View;
use Symfony\Component\HttpKernel\Exception\HttpException;

use App\Http\Requests\ServiceChangeRequest;
use App\Mail\{CancelRequest,ChangeRequest};
use App\Models\{Charge,Product,Service};

class ServiceController extends Controller
{
	/* SERVICE WORKFLOW METHODS */

	/**
	 * Cancel a request to cancel a service
	 *
	 * @param Service $o
	 * @return bool
	 */
	private function action_cancel_cancel(Service $o): bool
	{
		if (! $o->order_info)
			$o->order_info = collect();

		$o->order_info->put('cancel_cancel',Carbon::now()->format('Y-m-d H:i:s'));
		$o->order_status = 'ACTIVE';

		return $o->save();
	}

	/**
	 * Cancel a request to change a service
	 *
	 * @param Service $o
	 * @return bool
	 */
	private function action_change_cancel(Service $o): bool
	{
		if (! $o->order_info)
			$o->order_info = collect();

		$o->order_info->put('change_cancel',Carbon::now()->format('Y-m-d H:i:s'));
		$o->order_status = 'ACTIVE';

		return $o->save();
	}

	/**
	 * Action to change a service order_status to another stage
	 * This is a generic function that can redirect the user to a page that is required to completed to enter
	 * the new stage
	 *
	 * @param Service $o
	 * @param string  $stage
	 * @return \Illuminate\Contracts\Foundation\Application|RedirectResponse|\Illuminate\Routing\Redirector
	 */
	private function action_request_enter_redirect(Service $o,string $stage)
	{
		return redirect(sprintf('u/service/%d/%s',$o->id,strtolower($stage)));
	}

	/* OTHER METHODS */

	public function change_pending(ServiceChangeRequest $request,Service $o)
	{
		if ($request->post()) {
			foreach ($this->service_change_charges($request,$o) as $co)
				$co->save();

			$o->product_id = Arr::get($request->broadband,'product_id');
			$o->order_status = 'ACTIVE';
			$o->save();

			return redirect()->to(url('u/service',[$o->id]));
		}

		return view('a.service.change_pending')
			->with('o',$o);
	}

	/**
	 * Process a request to cancel a service
	 *
	 * @param Request $request
	 * @param Service $o
	 * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|\Illuminate\Routing\Redirector
	 */
	public function cancel_request(Request $request,Service $o)
	{
		if ($request->post()) {
			$request->validate([
				'stop_at'=>'required|date',
			]);

			if (! $o->order_info)
				$o->order_info = collect();

			$o->stop_at = $request->stop_at;
			$o->order_info->put('cancel_note',$request->notes);
			$o->order_status = 'CANCEL-REQUEST';
			$o->save();

			//@todo Get email from DB.
			Mail::to('help@graytech.net.au')
				->queue((new CancelRequest($o,$request->notes))->onQueue('email'));

			return redirect('u/service/'.$o->id)->with('success','Cancellation lodged');
		}

		return view('u.service.cancel_request')
			->with('o',$o);
	}

	/**
	 * Change the status of a service
	 * @todo This needs to be optimized
	 *
	 * @note This route is protected by middleware @see ServicePolicy::progress()
	 *       It is assumed that the next stage is valid for the services current stage - validated in ServicePolicy::progress()
	 * @param Service $o
	 * @param string  $stage
	 * @return RedirectResponse
	 */
	public function change(Service $o,string $stage): RedirectResponse
	{
		// While stage has a string value, that indicates the next stage we want to go to
		// If stage is NULL, the current stage hasnt been completed
		// If stage is FALSE, then the current stage failed, and may optionally be directed to another stage.

		while ($stage) {
			// Check that stage is a valid next action for the user currently performing it
			//$current = $this->getStageParameters($this->order_status);
			$next = $o->getStageParameters($stage);

			// If valid, call the method to confirm that the current stage is complete
			if ($x=$next->get('enter_method')) {
				if (! method_exists($this,$x))
					abort(500,sprintf('ENTER_METHOD [%s]defined doesnt exist',$x));

				Log::debug(sprintf('Running ENTER_METHOD [%s] on Service [%d] to go to stage [%s]',$x,$o->id,$stage));

				// @todo Should call exit_method of the current stage first, to be sure we can change

				try {
					$result = $this->{$x}($o,$stage);

					// If we have a form to complete, we need to return with a URL, so we can catch that with an Exception
				} catch (HttpException $e) {
					if ($e->getStatusCode() == 301)
						return ($e->getMessage());
				}

				// An Error Condition
				if (is_null($result))
					return redirect()->to('u/service/'.$o->id);

				elseif ($result instanceof RedirectResponse)
					return $result;

				// The service cannot enter the next stage
				elseif (! $result)
					abort(500,'Current Method FAILED: '.$result);

			} else {
				$o->order_status = $stage;

				if ($stage == 'ACTIVE')
					$o->active = TRUE;

				$o->save();
			}

			// If valid, call the method to start the next stage
			$stage = '';        // @todo this is temporary, we havent written the code to automatically jump to the next stage if wecan
		}

		return redirect()->to('u/service/'.$o->id);
	}

	/**
	 * Process a request to cancel a service
	 *
	 * @param Request $request
	 * @param Service $o
	 * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|\Illuminate\Routing\Redirector
	 */
	public function change_request(Request $request,Service $o)
	{
		if ($request->post()) {
			$request->validate([
				'change_date'=>'required|date',
				'notes'=>'required|min:10',
			]);

			if (! $o->order_info)
				$o->order_info = collect();

			$o->order_info->put('change_note',$request->notes);
			$o->order_info->put('change_date',$request->change_date);
			$o->order_info->put('change_product_id',$request->product_id);
			$o->order_status = 'CHANGE-REQUEST';
			$o->save();

			//@todo Get email from DB.
			Mail::to('help@graytech.net.au')
				->queue((new ChangeRequest($o,$request->notes))->onQueue('email'));

			return redirect('u/service/'.$o->id)->with('success','Upgrade requested');
		}

		switch (get_class($o->type)) {
			default:
				return view('u.service.change_request')
					->with('o',$o);
		}
	}

	/**
	 * Edit a domain service details
	 *
	 * @param Request $request
	 * @param Service $o
	 * @return RedirectResponse
	 * @deprecated - use update()
	 */
	public function domain_edit(Request $request,Service $o)
	{
		session()->flash('service_update',TRUE);

		$validation = $request->validate([
			'service.domain_name' => ['required',function($attribute,$value,$fail) use ($request,$o) {
				if (Service\Domain::where('domain_name',$value)
					->where('domain_tld_id',Arr::get($request,'service.domain_tld_id'))
					->when($o->type,function($q) use ($o) { return $q->where('id','<>',$o->type->id); })
					->count() > 0)
						{
							$fail('Domain already exists.');
						}
			}],
			'service.domain_expire' => 'required|date',
			'service.domain_tld_id' => 'required|exists:ab_domain_tld,id',
			'service.domain_registrar_id' => 'required|exists:ab_domain_registrar,id',
			'service.registrar_account' => 'required',
			'service.registrar_username' => 'required|string|min:5',
			'service.registrar_ns' => 'required|string|min:5',
		]);

		$o->type->forceFill($validation['service'])->save();

		return redirect()->back()->with('success','Record updated.');
	}

	/**
	 * List all the domains managed by the user
	 *
	 * @return View
	 * @todo revalidate
	 */
	public function domain_list(): View
	{
		$o = Service\Domain::serviceActive()
			->serviceUserAuthorised(Auth::user())
			->select('service_domain.*')
			->join('services',['services.id'=>'service_domain.service_id'])
			->with(['service.account','registrar'])
			->get();

		return view('r.service.domain.list')
			->with('o',$o);
	}

	public function email_list(): View
	{
		// @todo Need to add the with path when calculating next_billed and price
		$o = Service\Email::serviceActive()
			->serviceUserAuthorised(Auth::user())
			->select('service_email.*')
			->join('services',['services.id'=>'service_email.service_id'])
			->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
			->get();

		return view('r.service.email.list')
			->with('o',$o);
	}

	public function hosting_list(): View
	{
		// @todo Need to add the with path when calculating next_billed and price
		$o = Service\Host::serviceActive()
			->serviceUserAuthorised(Auth::user())
			->select('service_host.*')
			->join('services',['services.id'=>'service_host.service_id'])
			->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
			->get();

		return view('r.service.host.list')
			->with('o',$o);
	}

	private function service_change_charges(Request $request,Service $o): Collection
	{
		$charges = collect();
		$po = Product::findOrFail(Arr::get($request->broadband,'product_id'));
		$start_at = Carbon::create(Arr::get($request->broadband,'start_at'));

		// Get the invoiced items covering the start_at date
		foreach ($o->invoice_items->filter(function($item) use ($start_at) {
			return ($item->start_at < $start_at) && ($item->stop_at > $start_at) && ($item->item_type === 0);
		}) as $iio)
		{
			// Reverse the original charge
			$co = new Charge;
			$co->active = TRUE;
			$co->service_id = $o->id;
			$co->account_id = $o->account_id;
			$co->sweep_type = 6;
			$co->product_id = $iio->product_id;
			$co->description = 'Plan Upgrade Adjustment';
			$co->user_id = Auth::id();
			$co->type = $iio->item_type;
			$co->start_at = $start_at;
			$co->stop_at = $iio->stop_at;
			$co->amount = $iio->price_base;
			$co->taxable = TRUE;		// @todo this should be determined
			$co->quantity = -1*$start_at->diff($iio->stop_at)->days/$iio->start_at->diff($iio->stop_at)->days;
			$charges->push($co);

			// Add the new charge
			$co = new Charge;
			$co->active = TRUE;
			$co->service_id = $o->id;
			$co->account_id = $o->account_id;
			$co->sweep_type = 6;
			$co->product_id = $po->id;
			$co->description = 'Plan Upgrade Adjustment';
			$co->user_id = Auth::id();
			$co->type = $iio->item_type;
			$co->start_at = $start_at;
			$co->stop_at = $iio->stop_at;
			$co->amount = $po->base_charge;
			$co->taxable = TRUE;		// @todo this should be determined
			$co->quantity = $start_at->diff($iio->stop_at)->days/$iio->start_at->diff($iio->stop_at)->days;
			$charges->push($co);
		}

		// Add any fee
		if (Arr::get($request->broadband,'change_fee')) {
			$co = new Charge;
			$co->active = TRUE;
			$co->service_id = $o->id;
			$co->account_id = $o->account_id;
			$co->sweep_type = 6;
			$co->product_id = $po->id;
			$co->description = 'Plan Upgrade Fee';
			$co->user_id = Auth::id();
			$co->type = 3;
			$co->start_at = $start_at;
			$co->stop_at = $start_at;
			$co->amount = Arr::get($request->broadband,'change_fee');
			$co->taxable = TRUE;		// @todo this should be determined
			$co->quantity = 1;
			$charges->push($co);
		}

		return $charges;
	}

	/**
	 * This is an API method, that works with service change - to return the new charges as a result of changing a service
	 *
	 * @param Request $request
	 * @param Service $o
	 * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
	 */
	public function service_change_charges_display(Request $request,Service $o)
	{
		return view('a.charge.service_change')
			->with('charges',$this->service_change_charges($request,$o));
	}

	/**
	 * Update details about a service
	 *
	 * @param Request $request
	 * @param Service $o
	 * @return RedirectResponse
	 * @todo This needs to be reworked, to take into account our different service types
	 * @todo Add Validation
	 */
	public function update(Request $request,Service $o)
	{
		if ($request->post($o->product->category)) {
			$o->type->forceFill($request->post($o->product->category))->save();
		}

		if ($request->post('start_at'))
			$o->start_at = $request->start_at;

		$o->save();

		return redirect()->back()->with('success','Record Updated');
	}
}