<?php

namespace App\Http\Controllers;

use App\Helpers\Security;
use App\Interfaces\ReportRepositoryInterface;
use App\Rules\Fragments\PagedTable;
use Illuminate\Routing\Controller;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;


/**
 * This instance should not be modified or altered except if extending Creatia.
 * It merely exists as a means to provide extra functionality to applications.
 */
class BaseController extends Controller
{
    use AuthorizesRequests;

    private ReportRepositoryInterface $rr;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('api');
    }

    public function api_paged_table(Request $_r, Closure $_query, $_column, $_default_sort, ?Closure $_transform = null) {

        $_r->merge([
            'draw' => '1',
            'start' => '0',
            'length' => '0',
            'order_direction' => 'desc',
            'order_column' => null
        ]);

        return $this->paged_table($_r, $_query, $_column, $_default_sort, $_transform);
    }

    public function paged_table(Request $_r, Closure $_query, $_column, $_default_sort, ?Closure $_transform = null) {

        try {
            $validated = $_r->validate(PagedTable::rules(array_keys($_column), false));

            if($validated) {
                // The closure may run queries of its own while building (e.g. ownership lookups),
                // so it must only be invoked once - the counts below work on clones instead.
                $base = $_query();
                $query = clone $base;

                if($_column !== null) {

                    $global = null;
                    $filters = null;

                    $search = $_r->input('filter', []);

                    if(isset($search['global'])) {
                        $global = trim($search['global']);
                    }

                    if(isset($search['column'])) {
                        $filters = $search['column'];
                    }

                    // Build up global search criteria...
                    if($global !== null) {
                        $query = $query->where(function($q) use($_column, $global) {
                            foreach($_column as $attributes) {
                                if($attributes['searchable']) {

                                    if(is_array($attributes['db'])) {
                                        foreach($attributes['db'] as $condition) {
                                            $q->orWhere($condition, 'ILIKE', "%$global%");
                                        }
                                    }
                                    else {
                                        $q->orWhere($attributes['db'], 'ILIKE', "%$global%");
                                    }
                                }
                            }
                        });
                    }

                    // Build up specific search criteria...
                    if($filters !== null && count($filters) !== 0) {

                        $query = $query->where(function($q) use($_column, $filters) {
                            foreach($_column as $column => $attributes) {
                                if($attributes['searchable'] && isset($filters[$column])) {

                                    if(is_array($attributes['db'])) {
                                        $q->where(function($q2) use($filters, $column, $attributes) {
                                            foreach($attributes['db'] as $condition) {
                                                $q2->orWhere($condition, 'ILIKE', '%' . trim($filters[$column]) . '%');
                                            }
                                        });
                                    }
                                    else {
                                        $q->where($attributes['db'], 'ILIKE', '%' . trim($filters[$column]) . '%');
                                    }
                                }
                            }
                        });
                    }

                    // Apply sorting where relevant...
                    if($_r->input('order_column', false) && isset($_column[$_r->input('order_column')])) {
                        // This needs to be checked again to be absolutely sure. Everything must be done to protect the ordering. The validator has already handled this, but we need to be certain.
                        if(array_search($_r->input('order_column'), array_keys($_column)) !== false) {

                            // This is already assigned, laravel has already assigned it, but clean it. If it can't be cleaned, die. This MUST be safe. Unreasonable precautions need to be taken should Laravel's validator fail somehow.
                            $key = null;

                            if($_r->input('order_direction') === 'asc' || $_r->input('order_direction') === 'desc') {
                                $key = $_r->input('order_direction');
                            }
                            else {
                                die;
                            }

                            $query = $query->orderBy($_column[$_r->input('order_column')]['db'], $key);
                        }
                    }
                    else {
                        $query = $query->orderBy($_default_sort,  'DESC');
                    }
                }

                // Get full count, data, then current count
                $filter_count = $base->getQuery()->groups !== null ? DB::table(clone $base, 'a')->count() : $query->count();
                $data = $_r->input('length', 0) == 0 ? $query->get() : $query->skip($_r->input('start'))->take($_r->input('length'))->get();
                $total_count = $data->count();

                // Run transformation function...
                if($_transform !== null) {
                    $data = $_transform($data);
                }

                return response()->json([
                    'draw' => intval($_r->input('draw', 0)),
                    'recordsTotal' => $total_count,
                    'recordsFiltered' => $filter_count,
                    'data' => $data
                ], 200);
            }

        }
        catch(\Exception $ex) {
            \Log::debug($ex);
        }

    }

    protected function log_event($_signature, $_inputs, $_message = 'EMPTY') {

        $user = Auth::user();
        $ip = Security::real_ip();

        $user_string = "UNAUTHENTICATED [IP: $ip]";

        if($user !== null) {
            $user_string = "$user->email [UID: $user->id, OID: $user->organisation_id, IP: $ip]";
        }
        
        Log::info("($_signature) ($user_string) MSG: `$_message` WITH INPUT: " . json_encode($_inputs));
    }

    protected function log_error($_signature, $_inputs, $_error) {

        $user = Auth::user();
        $ip = Security::real_ip();

        $user_string = "UNAUTHENTICATED [IP: $ip]";

        if($user !== null) {
            $user_string = "$user->email [UID: $user->id, OID: $user->organisation_id, IP: $ip]";
        }

        $error_message = $_error->getMessage();
        $error_trace = $_error->getTraceAsString();
        
        Log::error("($_signature) ($user_string) MSG: \n\n$error_message\n\n$error_trace\n\n WITH INPUT: " . json_encode($_inputs));
    }

}
