<?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 AdminGeoIpController extends NetworkController
{
    const GEOIP_MODULE_PATH = '../app/Modules/Network/';

    public function get_geoips(Request $_r) {        
        $draw = $_r->input('draw');
        $start = $_r->input('start');
        $length = $_r->input('length');
        $search_term = $_r->input('search_string');

        // Build our query function
        $query_builder = function() {
            return Prefixes::select('prefixes.id', 'address', 'mask', 'organisations.name as organisation', 'prefixes.name as name', 'still_announced', 'country', 'region', 'city', 'whois_error', 'prefixes.organisation_id', 'asn')
                ->leftJoin('organisations', 'prefixes.organisation_id', '=', 'organisations.id')
                ->leftJoin('prefix_geo_ip', 'prefix_id', '=', 'prefixes.id')
                ->leftJoin('asns', 'prefixes.asn_id', '=', 'asns.id');
        };

        // 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('organisations.name', '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;
                        case 'organisation': $col = "organisation"; 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)->orderBy('mask');
                    break;
                case 'organisation':
                    $prefix_query = $prefix_query->orderBy('organisation', $direction)->orderBy('mask');
                    break;
                default:
                    $prefix_query = $prefix_query->orderBy('address', $direction ?? 'asc')->orderBy('mask');
            }

            return $prefix_query;
        };

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

        // 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);
    }

    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)->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) {
                $geo_ip_model = PrefixGeoIp::where('prefix_id', '=', $_geo_ip_id)->get()->first();
                
                $network = Prefixes::find($_geo_ip_id);
                $announced_supernet = Prefixes::getIsInAnnouncedSupernet($_geo_ip_id);

                if($network && ($network->still_announced == 1 || $announced_supernet)) {
                    // 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'); 

                    $geo_ip_model->fill($_r->input('country') == 0 ? [
                        'name'          => $_r->input('name', $geo_ip_model->name),
                        'country'       => null,
                        'region'        => null,
                        'city'          => null,
                        'whois_error'   => null
                    ] : [
                        'name'          => $_r->input('name', $geo_ip_model->name),
                        '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
                    ]);

                    $network->name = $_r->input('name');
    
                    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_metadata(Request $_r, $_organisation_id) {        
        try {
            $attributes = GeoIpOrganisationAttributes::where('organisation_id', '=', $_organisation_id)->first() ?? [];

            return response()->json([
                'allowed_supernets' => $attributes->allowed_supernets ?? 100,
                'allowed_subnets' => $attributes->allowed_subnets ?? 10,
            ], 200);
        }
        catch(Exception $ex) {}

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

    public function put_metadata(Request $_r, $_organisation_id) {
        try {
            $valid_request = !Validator::make([
                'supernet'      => $_r->input('supernet'), // < 24s
                'subnet'        => $_r->input('subnet'), // 32s
                'organisation'  => $_organisation_id
                ], [
                'supernet'      => ['required', 'integer', 'min:0'],
                'subnet'        => ['required', 'integer', 'min:0'],
                'organisation'  => ['required', 'exists:organisations,id']
            ])->fails();
    
            if($valid_request) {
                $attributes = GeoIpOrganisationAttributes::select('allowed_supernets', 'allowed_subnets')->where('organisation_id', '=', $_organisation_id)->first() ?? new GeoIpOrganisationAttributes;
    
                $attributes->fill([
                    'organisation_id'   => $_organisation_id,
                    'allowed_supernets' => $_r->input('supernet'),
                    'allowed_subnets'   => $_r->input('subnet'),
                ]);
    
                if($attributes->save()) {
                    return response()->json(['detail' => __('api.200_GENERIC')], 200);
                }

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

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

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

}
