<?php

namespace App\Http\Controllers\Organisation;

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

// Creatia Specifics
use App\Http\Controllers\BaseController;
use App\Models\Asns;
use App\Models\AsSets;
use App\Models\Organisation;
use App\Models\Suborganisation;
use App\Models\User;
use App\Models\OrganisationExternalIdentities;
use App\Models\Prefixes;
use App\Models\PrefixSubtenants;
use App\Models\ProductRights;
use App\Models\Supernets;
use App\Models\Webhook;
use App\Rules\AdminAnyAsnIdentity;
use App\Rules\AdminAnyAsSetIdentity;
use App\Rules\AsnFormat;
use App\Rules\AsSetFormat;
use App\Rules\ExternalApplication;
use App\Rules\GroupIdentity;
use App\Rules\OrganisationIdentity;
use App\Rules\UsageRight;
use Carbon\Carbon;
use Exception;

class AdminController extends BaseController
{
    public function get_as_set(Request $_r, $_organisation_id) {
        return $this->paged_table($_r, function() use($_organisation_id) {
            return AsSets::select('id', 'as_set', 'approved')->where('organisation_id', '=', $_organisation_id);
        }, [ 
            'as_set' => [ 'db' => 'as_set', 'searchable' => true ]
        ], 'as_set', null);
    }

    public function post_as_set(Request $_r, $_organisation_id) {
        $response = response()->json(__('api.500_GENERIC'), 500);

        $validation_rules = [
            'as_set' => ['required', AsSetFormat::make(false)],
        ];

        try {
            $as_set = $_r->input('as_set');

            if(!Validator::make($_r->all(), $validation_rules)->fails()) {

                $outcome = DB::transaction(function () use($as_set, $_organisation_id) {
                    return AsSets::make(strtoupper($as_set), $_organisation_id, true);
                });

                if($outcome) {
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(Exception $ex) {
            Log::debug($ex);
        }

        return $response;
    }

    public function delete_as_set(Request $_r, int $_as_set_id) {
        $response = response()->json(__('api.500_GENERIC'), 500);

        try {
            if(!Validator::make([ 'as_set' => $_as_set_id ], [ 'as_set' => ['required', new AdminAnyAsSetIdentity] ])->fails()) {
                if(AsSets::find($_as_set_id)->delete()) {
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(Exception $ex) {
            Log::debug($ex);
        }

        return $response;
    }

    public function get_parentless_organisation(Request $_r) {
        return $this->paged_table($_r, function() {
            return Organisation::select('id', 'name')->whereNotIn('id', function($q) {
                $q->select('child_id')->from('suborganisations');
            });
        }, [ 
            'name' => [ 'db' => 'name', 'searchable' => true ]
        ], 'name', null);
    }

    /**
     * Organisation Specific Controller Methods
     */

    public function get_organisation(Request $_r, $_organisation_id) {

        $organisation = Organisation::find($_organisation_id);
        
        $information = [
            'name'              => $organisation->name,
            'created_at'        => $organisation->created_at === null ? null : $organisation->created_at->format('Y-m-d\TH:i:s\Z'),
            'owned_prefixes'    => Prefixes::where('organisation_id', '=', $_organisation_id)->count(),
            'leased_prefixes'   => PrefixSubtenants::where('subtenant_id', '=', $_organisation_id)->select('prefix_id')->distinct()->count(),
            'children'          => Suborganisation::where('parent_id', '=', $_organisation_id)->count(),
            'webhooks'          => Webhook::where('organisation_id', '=', $_organisation_id)->count(),
        ];

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

    public function get_organisations(Request $_r) {
        return $this->paged_table($_r, function() use($_r) {

            $all = Organisation::withTrashed()->select(
                'id',
                'name',
                
                DB::raw('(SELECT COUNT(*) FROM users WHERE organisation_id = organisations.id AND users.deleted_at IS NULL) AS user_count'),
                DB::raw('(SELECT COUNT(*) FROM prefixes WHERE organisation_id = organisations.id AND prefixes.deleted_at IS NULL) AS owned_prefix_count'),
                DB::raw('(SELECT COUNT(DISTINCT prefix_id) FROM prefix_subtenants WHERE subtenant_id = organisations.id AND prefix_subtenants.deleted_at IS NULL) AS leased_prefix_count'),
                DB::raw('(SELECT COUNT(*) FROM suborganisations WHERE parent_id = organisations.id) AS child_count'),
                DB::raw('(SELECT COUNT(*) FROM asns WHERE organisation_id = organisations.id AND asns.deleted_at IS NULL) AS asn_count'),
                DB::raw('(SELECT COUNT(*) FROM as_sets WHERE organisation_id = organisations.id AND as_sets.deleted_at IS NULL) AS as_set_count'),
                DB::raw('(SELECT COUNT(*) FROM webhooks WHERE organisation_id = organisations.id AND webhooks.deleted_at IS NULL) AS webhook_count'),

                DB::raw('(SELECT string_agg(asn::text, \',\') from asns where organisation_id = organisations.id and deleted_at is null and approved = 1) as "asn_list"'),
                DB::raw('(SELECT string_agg(name, \',\') from product_rights where organisation_id = organisations.id and deleted_at is null) as "right_list"'),
                
            );

            $deleted = $_r->input('deleted');

            if($deleted === true || $deleted === 'true' || $deleted === '1') {
                return $all->whereNotNull('deleted_at');
            }
            else {
                return $all->whereNull('deleted_at');
            }
        }, [ 
            'name' => [ 'db' => 'name', 'searchable' => true ],
            'asn_list' => [ 'db' => DB::raw('(SELECT string_agg(asn::text, \',\') from asns where organisation_id = organisations.id and deleted_at is null and approved = 1)'), 'searchable' => true ],
            'right_list' => [ 'db' => DB::raw('(SELECT string_agg(name, \',\') from product_rights where organisation_id = organisations.id and deleted_at is null)'), 'searchable' => true ]
        ], 'name', function($data) {
    
            $data->transform(function($row) {
                $row['asn_list'] = $row['asn_list'] == "" ? [] : explode(',', $row['asn_list']);
                $row['right_list'] = $row['right_list'] == "" ? [] : explode(',', $row['right_list']);

                return $row;
            });

            return $data;
        });
    }

    public function get_suborganisations(Request $_r, $_organisation_id) {
        // Build our query function
        return $this->paged_table($_r, function() use($_organisation_id) {
            return Suborganisation::leftJoin('organisations', 'organisations.id', '=', 'suborganisations.child_id')
                ->where('suborganisations.parent_id', '=', $_organisation_id)
                ->whereNull('organisations.deleted_at')
                ->select('organisations.id', 'organisations.name');
        }, [ 
            'name' => [ 'db' => 'organisations.name', 'searchable' => true ]
        ], 'organisations.name', null);
    }

    public function get_users(Request $_r, $_organisation_id) {

        return $this->paged_table($_r, function() use($_organisation_id) {
            return User::select(DB::raw('users.id, users.name as name, email, groups.name as group'))->leftJoin('groups', 'groups.id', '=', 'users.group_id')->where('organisation_id', '=', $_organisation_id);
        }, [ 
            'name'  => [ 'db' => 'users.name', 'searchable' => true ],
            'group' => [ 'db' => 'groups.name', 'searchable' => true ],
            'email' => [ 'db' => 'email', 'searchable' => true ]
        ], 'name', null);
    }

    public function get_asns(Request $_r, int $_organisation_id) {
        return $this->paged_table($_r, function() use($_organisation_id) {
            return Asns::select(
                'id',
                'asn',
                'approved'
            )->where('organisation_id', $_organisation_id == 0 ? '!=' : '=', $_organisation_id);
        }, [ 
            'asn'       => [ 'db' => 'asn', 'searchable' => true ],
            'approved'  => [ 'db' => 'approved', 'searchable' => true ]
        ], 'asn', function($data) use($_organisation_id) {

            if($_organisation_id == 0) {
                $counts = Prefixes::select(DB::raw('COUNT(*) as x'), 'asn_id')
                    ->whereIn('asn_id', $data->pluck('id'))
                    ->whereRaw('family(address) = 4')
                    ->groupBy('asn_id')
                    ->get()
                    ->keyBy('asn_id');

                $names = Organisation::join('asns', 'asns.organisation_id', '=', 'organisations.id')
                    ->select('name', 'asns.id')
                    ->whereIn('asns.id', $data->pluck('id'))
                    ->get()
                    ->keyBy('id');


                $data->transform(function($row) use($counts, $names) {
                    $row['asn'] = $row['asn'] . ' - ' . $names[$row['id']]->name . ' (' . ($counts[$row['id']]->x ?? 0) . ' IPv4)';

                    return $row;
                });
            }

            return $data;

        });
    }

    public function post_asn(Request $_r, int $organisation_id) {
        $response = response()->json(__('api.500_GENERIC'), 500);

        $validation_rules = [
            'asn' => [
                'required',
                new AsnFormat
            ]
        ];

        try {
            $asn = $_r->input('asn');
            $routelist = $_r->input('routelist');

            if(!Validator::make($_r->all(), $validation_rules)->fails()) {
                $outcome = DB::transaction(function () use($asn, $routelist, $organisation_id) {
                    return Asns::make($asn, null, $routelist, $organisation_id, true);
                });

                if($outcome) {
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(Exception $ex) {}

        return $response;
    }

    public function delete_asn(Request $_r, int $_asn_id) {
        $response = response()->json(__('api.500_GENERIC'), 500);

        try {
            if(!Validator::make([ 'asn' => $_asn_id ], [ 'asn' => ['required', new AdminAnyAsnIdentity, Rule::notIn(Asns::whereIn('asn', [137409, 136510, 7578])->select('id')->get()->pluck('id'))] ])->fails()) {
                $asn = Asns::find($_asn_id);

                DB::beginTransaction();

                $asn->clean();

                // Delete AS-SETs nested under this ASN - The clean only re-homes them which isn't what we want in this situation.
                AsSets::where('as_set', 'LIKE', "AS$asn->asn:%")->delete();

                if($asn->delete()) {
                    DB::commit();
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    DB::rollBack();
                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(Exception $ex) {
            DB::rollBack();
            Log::debug($ex);
        }

        return $response;
    }

    public function post_organisation(Request $_r, $_parent_organisation_id = null) {
        $validation_rules = [
            'parent_organisation_id' => ['nullable', new OrganisationIdentity],
            'organisation_name' => ['required', 'string'],
            'user_name' => ['required', 'string'],
            'user_email' => ['required', 'email:rfc,dns', Rule::notIn(User::select('email')->get()->pluck('email'))],
            'group_id' => ['required', new GroupIdentity]

        ];

        $response = response()->json(['detail' => 'Error creating customer'], 500);
        
        try {
            if(!Validator::make(array_merge([ 'parent_organisation_id' => $_parent_organisation_id ], $_r->input()), $validation_rules)->fails()) {

                $outcome = DB::transaction(function () use($_parent_organisation_id, $_r) {
                    $organisation_id = Organisation::create([
                        'name' => $_r->input('organisation_name')
                    ])->id;

                    if($_parent_organisation_id !== null) {
                        Suborganisation::create([
                            'child_id' => $organisation_id,
                            'parent_id' => $_parent_organisation_id
                        ]);
                    }

                    $user = new User([
                        'name'              => $_r->input('user_name'),
                        'password'          => '',
                        'email'             => strtolower($_r->input('user_email')),
                        'group_id'          => $_r->input('group_id'),
                        'organisation_id'   => $organisation_id
                    ]);
        
                    if($user->save()) {
                        $user->sendSetupEmail();

                        return true;
                    }

                    return false;
                });

                if($outcome) {
                    $response = response()->json(['detail' => __('api.200_GENERIC')], 200);
                }
            }

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

        return $response;
    }

    public function post_existing_suborganisation(Request $_r, $organisation_id) {
        $validation_rules = [
            'child_id' => ['required', 'integer'],
        ];

        $response = response()->json(['detail' => 'Error adding existing sub-organisation'], 500);
        
        try {
            $child_id = $_r->input('child_id');

            $valid_organisations = Organisation::whereIn('id', [$organisation_id, $child_id])->count() === 2;

            if(!Validator::make($_r->all(), $validation_rules)->fails() && $valid_organisations) {
                $token = DB::transaction(function () use($organisation_id, $child_id) {
                    Suborganisation::create([
                        'child_id' => $child_id,
                        'parent_id' => $organisation_id
                    ]);
                });

                $response = response()->json(['email_token' => $token], 200);
            }

        } catch(Exception $ex) {}

        return $response;
    }

    public function delete_organisation(Request $_r, $_organisation_id) {
        $response = response()->json(['detail' => __('api.500_GENERIC')], 500);

        try {
            $valid_request = !Validator::make( array_merge([
                'organisation_id' => $_organisation_id
            ], $_r->all()), [
                'organisation_id' => [ 'required', OrganisationIdentity::make(true) ]
            ])->fails();

            if($valid_request) {

                $response = DB::transaction(function() use($_organisation_id) {

                    // The way we're handling it here allows re-deletion of trashed organisations should we have any stuck older assets.

                    try {

                        // Print a deletion summary;
                        Log::info(
                            "DELETION OF ORGANISATION"
                            . "\nSubtenancies: " . PrefixSubtenants::where('subtenant_id', '=', $_organisation_id)->count()
                            . "\nSupernets: " . Supernets::where('tenant_id', '=', $_organisation_id)->count()
                            . "\nPrefixes: " . Prefixes::where('organisation_id', '=', $_organisation_id)->count()
                            . "\nASNs: " . Asns::where('organisation_id', '=', $_organisation_id)->count()
                            . "\nAS-SETs: " . AsSets::where('organisation_id', '=', $_organisation_id)->count()
                            . "\nSuborganisation Relations: " . Suborganisation::where('parent_id', '=', $_organisation_id)->count()
                            . "\nUsers: " . User::where('organisation_id', '=', $_organisation_id)->count()
                            . "\nWebhooks: " . Webhook::where('organisation_id', '=', $_organisation_id)->count()
                            . "\nOrganisations: " . Organisation::where('id', '=', $_organisation_id)->count()
                        );

                        // Delete subtenancy status
                        PrefixSubtenants::where('subtenant_id', '=', $_organisation_id)->delete();

                        // Delete prefixes
                        Prefixes::where('organisation_id', '=', $_organisation_id)->get()->each->delete();

                        // Delete ASNs
                        Asns::where('organisation_id', '=', $_organisation_id)->delete();

                        // Delete AS-SETs
                        AsSets::where('organisation_id', '=', $_organisation_id)->delete();

                        // Delete suborganisation associations
                        Suborganisation::where('parent_id', '=', $_organisation_id)->delete();

                        // Delete users
                        User::where('organisation_id', '=', $_organisation_id)->update([ 'email' => DB::raw('CONCAT(email, CONCAT(\'-\', now()))'), 'deleted_at' => Carbon::now(), 'password' => 'Automated Removal' ]);

                        // Delete webhooks
                        Webhook::where('organisation_id', '=', $_organisation_id)->delete();

                        // Delete organisations
                        Organisation::where('id', '=', $_organisation_id)->delete();

                        return true;
                    }
                    catch (\Exception $e) {
                        Log::debug($e);
                        return false;
                    }

                }) ? response()->json(['detail' => __('api.200_GENERIC')], 200) : response()->json(['detail' => __('api.500_GENERIC')], 500);
            }
        }
        catch(Exception $ex) {
            Log::debug($ex);
        }

        return $response;
    }

    /**
     * Handlers for WHMCS ID
     */

    public function get_external_attributes(Request $_r, $_organisation_id) {

        $valid_request = !Validator::make([
            'organisation_id'         => $_organisation_id
        ], [
            'organisation_id'         => ['required', new OrganisationIdentity]
        ])->fails();

        if(!$valid_request) {
            return response()->json([], 400);
        }

        return response()->json(OrganisationExternalIdentities::where('organisation_id', '=', $_organisation_id)->select('key', 'identity')->get()->toArray() ?? [], 200);
    }

    public function post_external_identity(Request $_r, $_organisation_id) {
        $valid_request = !Validator::make([
            'organisation_id'         => $_organisation_id,
            ...$_r->all()
        ], [
            'organisation_id'       => ['required', new OrganisationIdentity],
            'identities.*.key'      => ['required', new ExternalApplication],
            'identities.*.identity' => ['required', 'integer']
        ])->fails();

        if(!$valid_request) {
            return response()->json([], 400);
        }

        try {
            DB::beginTransaction();

            // Remove existing identities
            OrganisationExternalIdentities::where('organisation_id', '=', $_organisation_id)->delete();

            foreach($_r->input('identities') as $identity) {
                OrganisationExternalIdentities::create([
                    'organisation_id'   => $_organisation_id,
                    'key'               => $identity['key'],
                    'identity'          => $identity['identity']
                ]);
            }

            DB::commit();
        }
        catch(Exception $ex) {
            DB::rollback();

            return response()->json([], 500);
        }

        return response()->json([], 200);
    }

    /**
     * Product Rights
     */

    public function get_rights(Request $_r, $_organisation_id) {

        $valid_request = !Validator::make([
            'organisation_id' => $_organisation_id,
        ], [
            'organisation_id' => ['required', new OrganisationIdentity],
        ])->fails();

        if(!$valid_request) {
            return response()->json([], 400);
        }

        return response()->json(ProductRights::whereOwned($_organisation_id)->distinct('name')->get()->pluck('name') ?? [], 200);
    }

    public function put_rights(Request $_r, $_organisation_id) {
        try {

            $valid_request = !Validator::make([
                ...$_r->input(),
                'organisation_id' => $_organisation_id,
            ], [
                'organisation_id'   => ['required', new OrganisationIdentity],
                'rights.*'          => [new UsageRight]
            ])->fails();

            if(!$valid_request) {
                return response()->json([], 400);
            }

            $to_insert = [];

            foreach($_r->input('rights') as $x) {
                $to_insert[] = [ 'name' => $x, 'organisation_id' => $_organisation_id ];
            }

            DB::beginTransaction();

            ProductRights::whereOwned($_organisation_id)->delete();
            ProductRights::insert($to_insert);            

            DB::commit();

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

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

}
