<?php

namespace App\Modules\Network\Controllers;

// Laravel Specifics
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

// Other
use Symfony\Component\Process\Process;

// Creatia Core
use App\Models\Asns;
use App\Models\Organisation;
use App\Models\Suborganisation;
use App\Helpers\Utility\RouteList;
use App\Helpers\Utility\WhoIs;

// Module Specifics
use App\Modules\Network\Controllers\NetworkController;
use App\Modules\Network\Models\Prefixes;
use App\Modules\Network\Models\GeoIp\PrefixGeoIp;
use App\Modules\Network\Models\GeoIp\GeoIpOrganisationAttributes;
use App\Modules\Network\Helpers\GeoIpControllerCommons as ControllerCommons;

use Log;

class GeoIpController extends NetworkController
{
    const GEOIP_MODULE_PATH = '../app/Modules/Network/';

    public function get_geoips(Request $_r, $_network_id = null) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;
        
        $draw = $_r->input('draw');
        $start = $_r->input('start');
        $length = $_r->input('length');
        
        // If user has access to anything.
        if($_network_id == null || Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_network_id)) {
            // Build our query function
            $query_builder = function() use($_network_id, $USER_ORGANISATION_ID) {
                // Get every single relevant prefix
                $prefixes_relevant = Prefixes::getOrganisationPrefixes($USER_ORGANISATION_ID)->pluck('id');

                // Construct our baseline query.
                $prefix_query = Prefixes::select('prefixes.id', 'address', 'mask', 'prefixes.name', 'still_announced', 'country', 'region', 'city', 'whois_error', 'prefixes.organisation_id', 'asn')
                    ->selectRaw('(SELECT COUNT(*) FROM supernets WHERE supernet_id = prefixes.id) AS "subnets"')
                    ->leftJoin('prefix_geo_ip', 'prefix_id', '=', 'prefixes.id')
                    ->leftJoin('asns', 'prefixes.asn_id', '=', 'asns.id')
                    ->whereIn('prefixes.id', $prefixes_relevant);

                // Thin it out to relevant subnets only if applicable.
                if($_network_id !== null) {
                    $prefix_query = $prefix_query->leftJoin('supernets', 'prefixes.id', '=', 'supernets.subnet_id')->where('supernets.supernet_id', '=', $_network_id);
                }

                return $prefix_query;
            };

            // Build our search function
            $query_search = function($prefix_query, $global, $filters) {

                if($global) {
                    $prefix_query = $prefix_query->where(function($query) use ($global){
                        $query->orWhere(DB::raw("CONCAT(address, '/', mask)"), 'LIKE', "%$global%")
                            ->orWhere('asn', 'like', "%$global%")
                            ->orWhere('prefixes.name', 'like', "%$global%");
                    });
                }

                if($filters) {
                    foreach($filters as $filter) {
                        $col = '';
                        switch($filter['id']) {
                            case 'ip': $col = DB::raw("CONCAT(address, '/', mask)"); break;
                            case 'name': $col = "prefixes.name"; break;
                        }

                        if($col != '') {
                            $prefix_query = $prefix_query->where($col, 'LIKE', '%' . $filter['value'] . '%');
                        }
                    }
                }

                return $prefix_query;
            };

            // Build our sorting function
            $query_sort = function($prefix_query, $column, $direction) {
                switch($column) {
                    case 'ip':
                        $prefix_query = $prefix_query->orderBy('address', $direction)->orderBy('mask');
                        break;
                    case 'name':
                        $prefix_query = $prefix_query->orderBy('name', $direction);
                        break;
                    case 'asn':
                        $prefix_query = $prefix_query->orderBy('asn', $direction);
                        break;
                    default:
                        $prefix_query = $prefix_query->orderBy('address', $direction ?? 'asc')->orderBy('mask');
                }

                return $prefix_query;
            };

            // Create valid sorting keys
            $sorting_keys = ['ip', 'name', 'updated_at', 'asn'];

            // Build our transformation function
            $query_transform = function($data) {
                $data->transform(function($row) {
                    return $row;
                });

                return $data;
            };

            return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, $sorting_keys);
        }

        return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
    }

    public function get_regions(Request $_r) {
        $raw_codes = json_decode(file_get_contents(self::GEOIP_MODULE_PATH . 'Models/GeoIp/RegionCodes.json'), true);

        return response()->json($raw_codes, 200);
    }

    public function get_data($_geo_ip_id) {
        try {
            if($x = PrefixGeoIp::where([['id', '=', $_geo_ip_id], ['organisation_id', '=', Auth::user()->organisation_id]])->first()) {
                return response()->json([
                    'name' => $x->name,
                    'country' => $x->country,
                    'region' => $x->region,
                    'city' => $x->city
                ], 200);
            }
        } catch(Exception $ex) {}

        return response()->json(['detail' => __('api.500_GENERIC')], 500);
    }

    public function put_data(Request $_r, $_geo_ip_id) {
        try {
            $region_lookup_array = json_decode(file_get_contents(self::GEOIP_MODULE_PATH . 'Models/GeoIp/RegionCodes.json'), true);
            $region_keys = array_keys($region_lookup_array);

            // Validate inputs
            $valid_request = !Validator::make([
                'name'          => $_r->input('name'),
                'country'       => $_r->input('country'),
                'region'        => $_r->input('region'),
                'city'          => $_r->input('city')
                ], [
                'name'          => ['string', 'nullable'],
                'country'       => ['required', Rule::in(array_merge($region_keys, ['0']))],
                'region'        => $_r->input('country') != 0 ? ['required', Rule::in(array_keys($region_lookup_array[$_r->input('country')]['divisions']))] : [],
                'city'          => $_r->input('country') != 0 ? ['required', Rule::in($region_lookup_array[$_r->input('country')]['divisions'][$_r->input('region')]['cities'])] : []
            ])->fails();

            // If valid, update
            if($valid_request) {
                $network = Prefixes::getOrganisationHasPrefix(Auth::user()->organisation_id, $_geo_ip_id);
                $geo_ip_model = PrefixGeoIp::where('prefix_id', '=', $_geo_ip_id)->first();
                $announced_supernet = Prefixes::getIsInAnnouncedSupernet($_geo_ip_id);

                Log::debug($network);

                if($network && ($network->still_announced == 1 || $announced_supernet)) {
                    // Determine whether the user is allowed to update this model.
                    $attrs = GeoIpOrganisationAttributes::where('organisation_id', '=', Auth::user()->organisation_id)->first() ?? new GeoIpOrganisationAttributes([
                        'allowed_subnets' => 10,
                        'allowed_supernets' => 100
                    ]);

                    $is_subnet = Prefixes::isTrueSubnet($_geo_ip_id);
                    $subnet_slots_not_free = $attrs->allowed_subnets <= PrefixGeoIp::countSetTrueSubnets(Auth::user()->organisation_id);
                    $supernet_slots_not_free = $attrs->allowed_supernets <= PrefixGeoIp::countSetTrueSupernets(Auth::user()->organisation_id);

                    if($is_subnet && $subnet_slots_not_free) { // Small
                        return response()->json(['detail' => __('api.409_UNAVAILABLE_SLOTS')], 409);      
                    }
                    else if(!$is_subnet && $supernet_slots_not_free) { // Large
                        return response()->json(['detail' => __('api.409_UNAVAILABLE_SLOTS')], 409);
                    }

                    // Determine who-is state
                    $who_is_country = WhoIs::location_lookup("$network->address/$network->mask");

                    $who_is_outcome = ($who_is_country == 'NO_ENTRY' || $who_is_country == 'NO_COUNTRY') ? $who_is_country : (array_search($_r->input('country',$geo_ip_model->country), $who_is_country) ? null : 'NOT_ALIGNED');

                    $network->name = $_r->input('name');

                    $geo_ip_model->fill($_r->input('country') == 0 ? [
                        'country'       => null,
                        'region'        => null,
                        'city'          => null,
                        'whois_error'   => null
                    ] : [
                        'country'       => $_r->input('country', $geo_ip_model->country),
                        'region'        => $_r->input('region', $geo_ip_model->region),
                        'city'          => $_r->input('city', $geo_ip_model->city),
                        'whois_error'   => $who_is_outcome
                    ]);
    
                    if($geo_ip_model->save() && $network->save()) {
                        return response()->json(['detail' => $who_is_outcome ? $who_is_outcome : __('api.200_GENERIC')], 200);
                    }
                    
                    return response()->json(['detail' => __('api.500_FAILURE')], 500);
                }

                return response()->json(['detail' => __('api.404_INVALID_IDENTIFIER', ['resource' => 'GeoIP'])], 404);
            }

            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        } catch(Exception $ex) {}

        return response()->json(['detail' => __('api.500_GENERIC')], 500);
    }

    public function get_geoip_metadata(Request $_r, $_geo_ip_id) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;

        try {
            $network = Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_geo_ip_id);

            if($network) {

                $count_small = PrefixGeoIp::countSetTrueSubnets($USER_ORGANISATION_ID);
                $count_large = PrefixGeoIp::countSetTrueSupernets($USER_ORGANISATION_ID);
    
                $attributes = GeoIpOrganisationAttributes::where('organisation_id', '=', Auth::user()->organisation_id)->first() ?? [];
    
                return response()->json([
                    'prefix_name' => $network->name,
                    'prefix_address' => "$network->address/$network->mask",
                    'valid_contributor' => Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_geo_ip_id),
                    'supernets_used' => $count_large ?? 0,
                    'subnets_used' => $count_small ?? 0,
                    'supernets_pool' => $attributes->allowed_supernets ?? 100,
                    'subnets_pool' => $attributes->allowed_subnets ?? 10,
                ], 200);
            }

            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }
        catch(Exception $ex) {}

        return response()->json(['detail' => __('api.500_GENERIC')], 500);
    }

    public function get_metadata(Request $_r) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;
        
        try {
            $count_large = PrefixGeoIp::countSetTrueSupernets($USER_ORGANISATION_ID);
            $count_small = PrefixGeoIp::countSetTrueSubnets($USER_ORGANISATION_ID);

            $attributes = GeoIpOrganisationAttributes::where('organisation_id', '=', Auth::user()->organisation_id)->first() ?? [];

            return response()->json([
                'supernets_used' => $count_large ?? 0,
                'subnets_used' => $count_small ?? 0,
                'supernets_pool' => $attributes->allowed_supernets ?? 100,
                'subnets_pool' => $attributes->allowed_subnets ?? 10,
            ], 200);
        }
        catch(Exception $ex) {}

        return response()->json(['detail' => __('api.500_GENERIC')], 500);
    }

    public function post_subnet(Request $_r, $_geo_ip_id) {
        $inputs = [
            'country' => $_r->input('country'),
            'region' => $_r->input('region'),
            'city' => $_r->input('city')
        ];

        $region_lookup_array = json_decode(file_get_contents(self::GEOIP_MODULE_PATH . 'Models/GeoIp/RegionCodes.json'), true);
        $region_keys = array_keys($region_lookup_array);

        $rules = [
            'country'       => ['required', Rule::in($region_keys)],
            'region'        => ['required', Rule::in(array_keys($region_lookup_array[$_r->input('country')]['divisions']))],
            'city'          => ['required', Rule::in($region_lookup_array[$_r->input('country')]['divisions'][$_r->input('region')]['cities'])]
        ];

        return NetworkController::post_prefix($_r, $_geo_ip_id, ['input' => $inputs, 'rule' => $rules ] , function($new, $parent, $request) {
            
            $who_is_country = WhoIs::location_lookup($request->input('cidr'));
            $who_is_outcome = ($who_is_country == 'NO_ENTRY' || $who_is_country == 'NO_COUNTRY') ? $who_is_country : (array_search($request->input('country', $new->country), $who_is_country) ? null : 'NOT_ALIGNED'); 

            PrefixGeoIp::create([
                'prefix_id'         => $new->id,
                'country'           => $request->input('country'),
                'region'            => $request->input('region'),
                'city'              => $request->input('city'),
                'organisation_id'   => $parent->organisation_id,
                'whois_error'       => $who_is_outcome
            ]);
        });
    }
}
