<?php

namespace App\Http\Controllers\Network;

use App\Http\Controllers\BaseController;
use App\Http\Controllers\Goliath\AdminGoliathController;
use App\Models\Asns;
use App\Models\AsSets;
use App\Models\Organisation;
use App\Models\Prefixes;
use App\Models\PrefixSubtenants;
use App\Models\Supernets;
use App\Rules\AdminHeuristicsLevel;
use App\Rules\BrandIdentity;
use App\Rules\Cidr;
use App\Rules\OrganisationIdentity;
use Illuminate\Validation\Rule;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

class AdminApiController extends BaseController
{

    public function associate_subnet(Request $_r, $_creatia_id) {
        // Validate inputs
        $valid_request = !Validator::make([
            'subnet'            => $_r->input('subnet'),
            'creatia_id'        => $_creatia_id,
            'name'              => $_r->input('name'),
            'prefix_only'       => $_r->input('prefix_only'),
            'children_allowed'  => $_r->input('children_allowed'),
            'override'          => $_r->input('override', false),
            'brand'             => $_r->input('brand', 'gsl')
        ], [
            'subnet'            => ['required', new Cidr],
            'creatia_id'        => ['required', new OrganisationIdentity],
            'name'              => ['required', 'string'],
            'prefix_only'       => ['required', 'boolean'],
            'children_allowed'  => ['required', 'boolean'],
            'override'          => ['required', 'boolean'],
            'brand'             => ['required', new BrandIdentity]
        ])->fails();

        try {
            if($valid_request) {

                // If the prefix does not exist, make it and the corresponding associations.
                $gsl_id = Organisation::where('name', '=', config('creatia.automation.main_organisation_name'))->orderBy('id', 'asc')->first()->id;
                $asn_id = Asns::where('asn', '=', '137409')->first()->id;

                $prefix = Prefixes::where([['organisation_id', '=', $gsl_id], ['mask', '>=', 22], [DB::raw('concat(address, \'/\', mask)'), '=', $_r->input('subnet')]])->first() ?? false;

                DB::beginTransaction();

                if($prefix) {
                    $prefix->delete();
                }

                $parents = Prefixes::where([['address', '!=', '2a09:8280:13::'], ['address', '!=', '2a01:b960:2301::'], ['address', '!=', '2a13:9500:1d::'], ['organisation_id', '=', $gsl_id], ['mask', '>=', 22], [DB::raw("concat(address, '/', mask)::cidr"), '>>', $_r->input('subnet')]])->get();

                if($parents->count() || $_r->input('override', false)) {

                    // Refuse to plant the range inside a carve-out somebody else holds beneath the GSL parent unless told to.
                    $contesting = Prefixes::getContestingPrefix($_r->input('subnet'), $parents->max('mask') ?? 0, $gsl_id);

                    if($contesting !== false) {

                        if(!$_r->input('override', false)) {
                            DB::rollback();

                            return response()->json(['detail' => 'You must provide `override` to place a `subnet` inside a range held by another organisation'], 409);
                        }

                        \Log::warning("Prefix {$_r->input('subnet')} placed inside contested range $contesting->address/$contesting->mask (#$contesting->id, Organisation #$contesting->organisation_id) by override.");
                    }

                    [$address, $mask] = explode('/', $_r->input('subnet'));

                    $prefix = Prefixes::create([
                        'name' => $_r->input('name'),
                        'address' => $address,
                        'mask' => $mask,
                        'created_source' => 'user',
                        'organisation_id' => $gsl_id,
                        'asn_id' => $_r->input('override', false) ? $asn_id : $parents[0]->asn_id,
                        'setup_required' => 1
                    ]);

                    $to_insert = [];

                    $carbon_now = Carbon::now();

                    foreach($parents as $parent) {

                        $to_insert[] = [
                            'created_at' => $carbon_now,
                            'updated_at' => $carbon_now,
                            'tenant_id' => $gsl_id,
                            'supernet_id' => $parent->id,
                            'subnet_id' => $prefix->id
                        ];

                        $to_insert[] = [
                            'created_at' => $carbon_now,
                            'updated_at' => $carbon_now,
                            'tenant_id' => $gsl_id,
                            'supernet_id' => null,
                            'subnet_id' => $prefix->id
                        ];
                    }

                    if(sizeof($to_insert)) {
                        DB::table('supernets')->insert($to_insert);
                    }
                }
                else {
                    DB::rollback();

                    return response()->json(['detail' => 'You must provide a valid `subnet` GET parameter that exists in a known GSL prefix'], 400);
                }
                
                // Erase subtenants so we can add in more
                PrefixSubtenants::where('prefix_id', '=', $prefix->id)->delete();

                if($_r->input('prefix_only')) {
                    DB::table('prefix_subtenants')->insert([
                        'prefix_id' => $prefix->id,
                        'subtenant_id' => $_creatia_id,
                        'created_at' => Carbon::now(),
                        'updated_at' => Carbon::now(),
                        'also_subnets' => 0,
                    ]);
                }

                if($_r->input('children_allowed')) {
                    DB::table('prefix_subtenants')->insert([
                        'prefix_id' => $prefix->id,
                        'subtenant_id' => $_creatia_id,
                        'created_at' => Carbon::now(),
                        'updated_at' => Carbon::now(),
                        'also_subnets' => 1,
                    ]);
                }

                // Erase known subnets
                \Log::info("The following need their details purged. They are being deleted.");

                $nested_subnets = Prefixes::whereRaw('?::cidr >> CONCAT(address, \'/\', mask)::cidr', [ $_r->input('subnet') ])->where([['mask', '>=', 25], ['created_source', '=', 'user'], ['organisation_id', '=', 1]])->get();

                \Log::debug($nested_subnets);

                foreach($nested_subnets as $x) {
                    if(($to_delete = Prefixes::find($x->id)) && $to_delete !== null) {
                        $to_delete->delete();
                    }
                }

                if($_r->input('brand', 'gsl') !== 'gsl') {
                    $subtenant_id = $_r->input('brand') === 'go-dedicated' ?
                        Organisation::where('name', '=', 'Go Dedicated')->orderBy('id', 'asc')->first()->id :
                        Organisation::where('name', '=', 'Streamline Servers')->orderBy('id', 'asc')->first()->id;
                    
                    DB::table('prefix_subtenants')->insert([[
                        'prefix_id' => $prefix->id,
                        'subtenant_id' => $subtenant_id,
                        'created_at' => Carbon::now(),
                        'updated_at' => Carbon::now(),
                        'also_subnets' => 1,
                    ], [
                        'prefix_id' => $prefix->id,
                        'subtenant_id' => $subtenant_id,
                        'created_at' => Carbon::now(),
                        'updated_at' => Carbon::now(),
                        'also_subnets' => 0,
                    ]]);
                }

                DB::commit();

                return response()->json(['detail' => $prefix->id], 200);
            }

            return response()->json(['detail' => 'You must provide a valid `subnet` GET parameter and valid `creatia_id` slug for all requests.'], 400);
        }
        catch(\Exception $x) {
            \Log::debug($x);
            DB::rollback();
            return response()->json(['detail' => 'A server error has occured. :('], 500);
        }
    }

    public function get_subnets(Request $_r, $_creatia_id) {
        // Validate inputs
        $valid_request = !Validator::make([
            'creatia_id' => $_creatia_id
        ], [
            'creatia_id' => ['required', new OrganisationIdentity]
        ])->fails();

        if($valid_request) {
            return $this->api_paged_table($_r, function() use($_creatia_id) {
                return Prefixes::leftJoin('prefix_subtenants', 'prefix_subtenants.prefix_id', '=', 'prefixes.id')
                    ->leftJoin('prefix_goliath_flags', 'prefix_goliath_flags.prefix_id', '=', 'prefixes.id')
                    ->leftJoin('prefix_goliath_inline', 'prefix_goliath_inline.prefix_id', '=', 'prefixes.id')
                    ->leftJoin('prefix_goliath_firewall', 'prefix_goliath_firewall.prefix_id', '=', 'prefixes.id')
                    ->leftJoin('goliath_rulesets', 'prefix_goliath_firewall.ruleset_id', '=', 'goliath_rulesets.id')
                    ->whereNull('prefix_goliath_flags.region_id')
                    ->whereNull('prefix_goliath_flags.subregion_id')
                    ->whereNull('prefix_goliath_firewall.region_id')
                    ->whereNull('prefix_goliath_firewall.subregion_id')
                    ->whereNull('prefix_subtenants.deleted_at')
                    ->where([['subtenant_id', '=', $_creatia_id], ['prefixes.organisation_id', '=', Organisation::where('name', '=', config('creatia.automation.main_organisation_name'))->orderBy('id', 'asc')->first()->id], ['mask', '>=', 22]])
                    ->select(
                        DB::raw('concat(address, \'/\', mask) as prefix'),
                        'prefixes.id as prefix_id',
                        'prefixes.name as name',
                        'flag_heuristics_level as l1_heuristics',
                        'flag_synproxy as l1_synproxy',
                        'flag_filters as l2_filters',
                        'forced as l1_inline',
                        'goliath_rulesets.id as l2_ruleset_id',
                        'goliath_rulesets.name as l2_ruleset_name'
                    )
                    ->distinct('prefixes.id'); 

            }, [], 'prefixes.id', null);
        }

        return response()->json(['detail' => 'You must provide a `creatia_id` slug for all requests.'], 400);
    }

    public function clean_subnet(Request $_r, $_prefix_id) {
        // Validate inputs
        $valid_request = !Validator::make([
            'prefix_id' => $_prefix_id
        ], [
            'prefix_id' => ['required', Rule::in(Prefixes::whereIn('id', PrefixSubtenants::where('prefix_id', '=', $_prefix_id)->get()->pluck('prefix_id'))->where([['organisation_id', '=', 1], ['mask', '>=', 22], ['id', '=', $_prefix_id]])->get()->pluck('id'))]
        ])->fails();

        if($valid_request) {

            try {
                DB::beginTransaction();

                // Mark child prefixes as requiring setup...
                Prefixes::whereIn('id', Supernets::where('supernet_id', '=', $_prefix_id)->pluck('subnet_id'))->where('organisation_id', '=', 1)->update([
                    'name'              => null,
                    'setup_required'    => 1
                ]);

                // Cleanup the prefix in question
                Prefixes::where([['organisation_id', '=', 1], ['mask', '>=', 22], ['id', '=', $_prefix_id]])->update([
                    'name'              => null,
                    'setup_required'    => 1
                ]);

                // Destroy subtenant associations
                PrefixSubtenants::where('prefix_id', '=', $_prefix_id)->delete();

                DB::commit();

                return response()->json(['detail' => 'Subnet cleaned.'], 200);
            }
            catch(\Exception $x) {
                DB::rollback();
            }
        }

        return response()->json(['detail' => 'You must provide a `prefix_id` slug for all requests. The `prefix_id` must be owned by GSL, and have a valid subtenant.'], 400);
    }

    public function get_approved_as(Request $_r) {
        return response()->json([ ...Asns::join('organisations', 'organisations.id', 'asns.organisation_id')->where('organisation_id', '!=', 1)->where('approved', '=', 1)->select( 'asn' )->get()->pluck('asn'), ...AsSets::join('organisations', 'organisations.id', 'as_sets.organisation_id')->where('approved', '=', 1)->select( 'as_set' )->get()->pluck('as_set') ], 200);
    }

    public function upsert_mitigation(Request $_r) {
        // Validate inputs
        $valid_request = !Validator::make($_r->input(), [
            'prefix'            => ['required', new Cidr],
            'name'              => ['nullable', 'string'],
            'inline'            => ['nullable', 'boolean'],
            'heuristics'        => ['nullable', new AdminHeuristicsLevel],
            'synproxy'          => ['nullable', 'boolean'],
            'origin_aware'      => ['nullable', 'boolean']

        ])->fails();

        try {
            if($valid_request) {

                // Find the prefix. If it doesn't exist, enter the creation path.
                $prefix = Prefixes::where(DB::raw('concat(address, \'/\', mask)'), '=', $_r->input('prefix'))->first() ?? false;

                if($prefix) {

                    $goliath_controller = new AdminGoliathController;

                    $existing_flags = $goliath_controller->get_flags(new Request(), $prefix->id)->original;

                    $goliath_controller->put_flags(new Request([
                        'inline'                => $_r->input('inline', $existing_flags['inline']),
                        'synproxy'              => $_r->input('synproxy', $existing_flags['synproxy']),
                        'filters'               => $existing_flags['filters'],
                        'loose'                 => $_r->input('origin_aware', $existing_flags['loose']),
                        'heuristics'            => $_r->input('heuristics', $existing_flags['heuristics_level']) === 0 ? false : true,
                        'heuristics_level'      => $_r->input('heuristics', $existing_flags['heuristics_level']),
                        'block_tcp'             => $existing_flags['block_tcp'],
                        'block_udp'             => $existing_flags['block_udp'],
                        'block_fragments'       => $existing_flags['block_fragments'],
                        'syn_cookies'           => $existing_flags['syn_cookies'],
                        'drop_tcp_out_of_state' => $existing_flags['drop_tcp_out_of_state'],
                        'app_filter_strict'     => $existing_flags['app_filter_strict'],
                        'syn_cookie_data_promote'   => $existing_flags['syn_cookie_data_promote'],
                        'syn_cookie_os_gate'        => $existing_flags['syn_cookie_os_gate'],
                        'syn_challenge'             => $existing_flags['syn_challenge'],
                        'syn_drop_first'            => $existing_flags['syn_drop_first'],
                        'src_limit_pps'             => $existing_flags['src_limit_pps'],
                        'src_limit_mbps'            => $existing_flags['src_limit_mbps'],
                        'drop_unexpected_protocols' => $existing_flags['drop_unexpected_protocols'],
                        'drop_non_core_protocols'   => $existing_flags['drop_non_core_protocols'],
                        'drop_udp_to_tcp_ports'     => $existing_flags['drop_udp_to_tcp_ports'],
                        'udp_established_lane'      => $existing_flags['udp_established_lane']
                    ]), $prefix->id);

                }
                else {
                    $parent_prefix = Prefixes::where(DB::raw('concat(address, \'/\', mask)::cidr'), '>>', $_r->input('prefix'))->first() ?? false;

                    if(!$parent_prefix) {
                        // Fail here
                        return response()->json(['detail' => 'No parent prefix found.'], 500);
                    }

                    // Retrieve defauts

                    $goliath_controller = new AdminGoliathController;

                    $defaults = $goliath_controller->get_defaults(new Request(), $parent_prefix->asn_id)->original;

                    $goliath_controller->post_subnet(new Request([
                        
                        'cidr'              => $_r->input('prefix'),
                        'name'              => $_r->input('name', 'Automated Protection'),
                        'synproxy'          => $_r->input('synproxy', $defaults['synproxy']),
                        'fivem'             => false,
                        'loose'             => $_r->input('origin_aware', $defaults['loose']),
                        'heuristics'        => $defaults['heuristics'] === 0 ? false : true,
                        'inline'            => $_r->input('inline', $defaults['inline']),
                        'heuristics_level'  => $_r->input('heuristics', $defaults['heuristics'] === 0 ? null : $defaults['heuristics'])

                    ]), $parent_prefix->id);

                    $prefix = Prefixes::where(DB::raw('concat(address, \'/\', mask)'), '=', $_r->input('prefix'))->first();
                }

                return response()->json(['detail' => $prefix->id], 200);
            }

            return response()->json(['detail' => 'You must provide valid parameters for all requests.'], 400);
        }
        catch(\Exception $x) {
            \Log::debug($x);
            return response()->json(['detail' => 'A server error has occurred.'], 500);
        }
    }
}