<?php

namespace App\Http\Controllers\Organisation;

use App\Http\Controllers\BaseController;
use App\Models\Goliath\GoliathOrganisationAttributes;
use App\Models\Group;
use App\Models\Mercury\MercuryOrganisationAttributes;
use App\Models\Organisation;
use App\Models\OrganisationExternalIdentities;
use App\Models\ProductRights;
use App\Models\Suborganisation;
use App\Models\User;
use App\Notifications\SlackNotification;
use App\Rules\BrandIdentity;
use App\Rules\OrganisationIdentity;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class AdminApiController extends BaseController
{

    public function get_external_identities(Request $_r) {

        if($_r->input('application', false)) {
            return $this->api_paged_table($_r, function() use($_r) {

                if($_r->input('identity', false)) {
                    return OrganisationExternalIdentities::join('organisations', 'organisation_id', '=', 'organisations.id')
                        ->where([
                            ['key', '=', $_r->input('application')],
                            ['identity', '=', $_r->input('identity')],
                        ])
                        ->whereNull('organisations.deleted_at')
                        ->select('organisation_id as creatia_id', 'key as external_tag', 'identity as external_id');
                }

                return OrganisationExternalIdentities::join('organisations', 'organisation_id', '=', 'organisations.id')
                    ->where('key', '=', $_r->input('application'))
                    ->whereNull('organisations.deleted_at')
                    ->select('organisation_id as creatia_id', 'key as external_tag', 'identity as external_id');

            }, [], 'organisation_id', null);
        }

        return response()->json(['detail' => 'You must provide an `application` GET parameter for all requests and optionally an `identity` GET parameter if you want a specific creatia_id.'], 400);
    }

    public function create_organisation(Request $_r) {

        $valid_request = !Validator::make([
            'name'              => $_r->input('name'),
            'identifiers'       => $_r->input('identifiers'),
            'subtenant'         => $_r->input('subtenant')
        ], [
            'name'                   => ['required', 'string'],
            'identifiers.*.key'      => ['required', 'string'],
            'identifiers.*.identity' => ['required', 'string'],
            'subtenant'              => ['required', new BrandIdentity]
        ])->fails();

        if($valid_request) {

            try {
                DB::beginTransaction();

                // To create an organisation, you must provide ONE external identifier
                $suffix = $_r->input('subtenant') === 'go-dedicated' ? '[DEDI-GD]' : ( $_r->input('subtenant') === 'streamline' ? '[DEDI-SL]' : '' );

                $organisation_id = Organisation::create([
                    'name' => $_r->input('name') . ( $suffix === '' ? '' : " $suffix" )
                ])->id;

                // Create external identifiers
                $external_ids = [];

                foreach($_r->input('identifiers') as $x) {
                    $external_ids[] = [
                        'key'               => $x['key'],
                        'identity'          => $x['identity'],
                        'organisation_id'   => $organisation_id,
                        'created_at'        => Carbon::now(),
                        'updated_at'        => Carbon::now(),
                    ];
                }

                $check_valid_query = OrganisationExternalIdentities::join('organisations', 'organisation_id', '=', 'organisations.id')
                    ->whereNull('organisations.deleted_at')
                    ->where(function($q) use($_r) {
                        foreach($_r->input('identifiers') as $x) {
                            $q->orWhere(function($q2) use($x) {
                                $q2->where([
                                    ['organisation_external_identities.key', '=', $x['key']],
                                    ['organisation_external_identities.identity', '=', $x['identity']],
                                ]);
                            });
                        }
                    });

                if($check_valid_query->count() === 0) {

                    if($external_ids !== []) {
                        OrganisationExternalIdentities::insert($external_ids);
                    }

                    MercuryOrganisationAttributes::create([
                        'organisation_id'           => $organisation_id,
                        'allow_high'                => false,
                        'allow_announcements'       => true,
                        'allow_downstreaming'       => false
                    ]);

                    GoliathOrganisationAttributes::create([
                        'organisation_id' => $organisation_id
                    ]);


                    if($_r->input('subtenant', false)) {
                        switch($_r->input('subtenant')) {
                            case 'go-dedicated':
                                Suborganisation::create([
                                    'parent_id' => Organisation::where('name', '=', 'Go Dedicated')->orderBy('id', 'asc')->first()->id,
                                    'child_id'  => $organisation_id
                                ]);
                                break;
                            case 'streamline':
                                Suborganisation::create([
                                    'parent_id' => Organisation::where('name', '=', 'Streamline Servers')->orderBy('id', 'asc')->first()->id,
                                    'child_id'  => $organisation_id
                                ]);
                                break;
                        }
                    }

                    Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification("Organisation has been provisioned successfully. Company: " . $_r->input('name')));

                    DB::commit();

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

                DB::rollback();

                return response()->json(['detail' => 'A provided external identifier already exists.'], 400);
            }
            catch(\Exception $x) {
                DB::rollback();

                \Log::debug($x);

                return response()->json(['detail' => 'An error has occurred.'], 500);
            }
        }

        return response()->json(['detail' => 'You must provide valid `identifiers` and a valid `name`'], 400);
    }

    /**
     * Resolve which brand an organisation sits under.
     *
     * Mirrors the parenting applied by create_organisation()/provision_dedi(): a brand is
     * expressed as a Suborganisation row whose parent is the brand's own organisation, and
     * an organisation with no brand parent belongs to 'gsl'. Suborganisation is also used
     * for ordinary customer hierarchies, so only known brand parents are considered, and
     * the chain is walked rather than only the direct parent - a customer nested one level
     * under a brand is still that brand's.
     *
     * Returns null rather than guessing when the brand organisations cannot be found by
     * name. Callers use this to decide whether two things belong to the same tenancy, and
     * 'gsl' is the common case, so a lookup failure that answered 'gsl' would silently
     * satisfy that comparison instead of failing it.
     *
     * @param int $_organisation_id
     * @return string|null One of 'gsl', 'go-dedicated', 'streamline', or null if unknown.
     */
    private function resolve_subtenant($_organisation_id) {

        // users.organisation_id is nullable, and where('child_id','=',null) becomes IS NULL,
        // which matches nothing and would fall through to 'gsl' - the one answer the
        // docblock promises never to guess.
        if ($_organisation_id === null) {
            return null;
        }

        $brands = [ 'Go Dedicated' => 'go-dedicated', 'Streamline Servers' => 'streamline' ];

        // Lowest ID per name, matching create_organisation() and provision_dedi(). The
        // organisation name is caller supplied - the button passes the client's company
        // name straight through - so a customer trading as "Go Dedicated" would otherwise
        // register as a brand parent and skew every lookup.
        $brand_parents = [];

        foreach ($brands as $name => $slug) {

            $organisation = Organisation::where('name', '=', $name)->orderBy('id', 'asc')->first();

            if ($organisation === null) {
                return null;
            }

            $brand_parents[$organisation->id] = $slug;
        }

        // Walk upwards. The depth cap is a cycle guard - suborganisations are not
        // constrained to a tree, so a loop would otherwise spin here.
        $seen = [];
        $current = $_organisation_id;

        for ($depth = 0; $depth < 16; $depth++) {

            if (isset($seen[$current])) {
                return null;
            }

            $seen[$current] = true;

            $parents = Suborganisation::where('child_id', '=', $current)->pluck('parent_id');

            if ($parents->isEmpty()) {
                return 'gsl';
            }

            foreach ($parents as $parent_id) {
                if (isset($brand_parents[$parent_id])) {
                    return $brand_parents[$parent_id];
                }
            }

            $current = $parents->first();
        }

        return null;
    }

    public function get_user(Request $_r) {
         if($_r->input('email', false)) {

            // Case insensitive on purpose: callers compare this answer against addresses
            // held in WHMCS, and Postgres '=' is exact - a stored 'Bob@x.com' against a
            // supplied 'bob@x.com' would report no user and strand the customer.
            // orderBy is not cosmetic: users.email is unique but Postgres uniqueness is
            // case sensitive, so Bob@x.com and bob@x.com can both exist in different
            // organisations. Callers branch on which organisation comes back, so it has to
            // be the same one every time.
            $user = User::whereRaw('lower(email) = ?', [ strtolower(trim($_r->input('email'))) ])
                ->orderBy('id', 'asc')
                ->first();

            if($user) {

                return response()->json([
                    'user' => $user->email,
                    'organisation_id' => $user->organisation_id,
                    'subtenant' => $this->resolve_subtenant($user->organisation_id)
                ], 200);

            }

            return response()->json([ 'user' => null, 'organisation_id' => null, 'subtenant' => null ], 200);

        }

        return response()->json(['detail' => 'You must provide an `email` GET parameter for all requests if you want a specific user and their organisation_id.'], 400);
    }

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

        $valid_request = !Validator::make([
            'organisation_id'   => $_organisation_id,
            'email'             => $_r->input('email'),
            'name'              => $_r->input('name')
        ], [
            'organisation_id'   => ['required', new OrganisationIdentity],
            'email'             => ['required', 'email:rfc,dns'],
            'name'              => ['required', 'string'],
        ])->fails();

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {
            $organisation_name = Organisation::find($_organisation_id)->name;

            $user_email = $_r->input('email');
            $user_name = $_r->input('name');

            $user = new User([
                'name'              => $user_name,
                'password'          => '',
                'email'             => $user_email,
                'group_id'          => Group::where('name', '=', 'Creatia Standard User')->first()->id,
                'organisation_id'   => $_organisation_id
            ]);

            if($user->save()) {

                $user->sendSetupEmail();

                Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification("Account has been provisioned successfully. Company: $organisation_name, Name: $user_name, Email: $user_email"));

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

        }

        return response()->json(['detail' => 'You need to provide valid `email` and `name` parameters, alongside a valid `organisation_id` slug.'], 400);
    }

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

        $valid_request = !Validator::make([
            'organisation_id'   => $_organisation_id,
            'email'             => $_r->input('email'),
            'name'              => $_r->input('name'),
            'readonly'         => $_r->input('readonly')
        ], [
            'organisation_id'   => ['required', new OrganisationIdentity],
            'email'             => ['required', 'email:rfc,dns'],
            'name'              => ['required', 'string'],
            'readonly'         => ['required', 'boolean']
        ])->fails();

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {
            $organisation_name = Organisation::find($_organisation_id)->name;

            $user_email = $_r->input('email');
            $user_name = $_r->input('name');

            $user = new User([
                'name'              => $user_name,
                'password'          => '',
                'email'             => $user_email,
                'group_id'          => Group::where('name', '=', ( $_r->input('readonly') ? 'Creatia Read-Only User' : 'Creatia Standard User'))->first()->id,
                'organisation_id'   => $_organisation_id
            ]);

            if($user->save()) {

                $user->sendSetupEmail();

                Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification("Account has been provisioned successfully. Company: $organisation_name, Name: $user_name, Email: $user_email"));

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

        }

        return response()->json(['detail' => 'You need to provide valid `email` and `name` parameters, alongside a valid `organisation_id` slug.'], 400);
    }

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

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

            'identifiers.*.key'      => ['required', 'string'],
            'identifiers.*.identity' => ['required', 'string'],
        ])->fails();

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {

            foreach($_r->input('identifiers') as $identifier) {
                
                $x = OrganisationExternalIdentities::firstOrNew([
                    'key'               => $identifier['key'],
                    'organisation_id'   => $_organisation_id
                ]);

                $x->identity = $identifier['identity'];
                $x->save();
            }

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

        return response()->json(['detail' => 'You need to provide a valid `identifier` parameter, alongside a valid `organisation_id` slug.'], 400);
    }

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

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

            'identifiers.*.key'      => ['required', 'string'],
            'identifiers.*.identity' => ['required', 'string'],
            'identifiers.*.mode'     => ['required', Rule::in([ 'add', 'remove' ])]
        ])->fails();

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {

            foreach($_r->input('identifiers') as $identifier) {

                switch($identifier['mode']) {
                    case 'add':
                        
                        OrganisationExternalIdentities::create([
                            'key'               => $identifier['key'],
                            'identity'          => $identifier['identity'],
                            'organisation_id'   => $_organisation_id
                        ]);

                        break;
                    case 'remove':
                        
                        OrganisationExternalIdentities::where([
                            [ 'key', '=', $identifier['key'] ],
                            [ 'identity', '=', $identifier['identity'] ],
                            [ 'organisation_id', '=', $_organisation_id ]
                        ])->delete();

                        break;
                }

            }

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

        return response()->json(['detail' => 'You need to provide a valid `identifier` parameter, alongside a valid `organisation_id` slug.'], 400);
    }

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

        $valid_request = !Validator::make([
            'organisation_id'   => $_organisation_id,
            'bandwidth'         => $_r->input('bandwidth'),
        ], [
            'organisation_id'   => ['required', new OrganisationIdentity],
            'bandwidth'         => ['required', 'numeric', 'min:0']
        ])->fails();

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {

            $x = MercuryOrganisationAttributes::firstOrNew([
                'organisation_id'           => $_organisation_id
            ]);

            $x->aggregate_bandwidth = $_r->input('bandwidth');
            $x->save();

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

        return response()->json(['detail' => 'You need to provide a valid `bandwidth` parameter, alongside a valid `organisation_id` slug.'], 400);
    }

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

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

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {

            (ProductRights::firstOrNew([
                'organisation_id'   => $_organisation_id,
                'name'              => $_r->input('right')
            ]))->save();

            // Elevate permissions for dedi purchases as they may have a read-only account.
            //
            // Deliberately not extended to other rights: this promotes EVERY read-only user
            // in the organisation, not the purchaser, and read-only is a role a customer
            // assigns to their own colleagues on purpose. Releasing a button-provisioned
            // read-only user needs a per-user mechanism, not a wider trigger here.
            if(array_search($_r->input('right'), ['dedi-go-dedicated', 'dedi-streamline']) !== false) {

                User::where([
                    [ 'organisation_id', '=', $_organisation_id ],
                    [ 'group_id', '=', Group::where('name', '=', 'Creatia Read-Only User')->first()->id ]
                ])
                ->update([
                    'group_id' => Group::where('name', '=', 'Creatia Standard User')->first()->id 
                ]);

            }

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

        return response()->json(['detail' => 'You need to provide a valid `right` parameter, alongside a valid `organisation_id` slug.'], 400);
        
    }

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

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

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {

            // Delete whatever the right is, then execute product specific things
            ProductRights::where([ ['organisation_id', '=', $_organisation_id], ['name', '=', $_r->input('right')] ])->delete();

            // Get organisation information
            $org = Organisation::find($_organisation_id);

            if($org === null) {
                return response()->json(['detail' => 'SUCCESS'], 200);
            }

            // This is a switch because I'm assuming this could grow quite a bit.
            switch($_r->input('right')) {
                case 'mercury': // Delete all tunnels for Mercury

                    // Retrieve GSL CXID
                    $cxids = OrganisationExternalIdentities::where([ ['key', '=', 'gsl'], ['organisation_id', '=', $_organisation_id] ])->select('identity')->get()->pluck('identity')->toArray();

                    $all_status_codes = [];
                    
                    foreach($cxids as $cxid) {
                        $tunnel_deletion = Http::withHeaders(['Content-Type' => 'application/json'])
                            ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
                            ->delete(env('tunnel_microservice_url') . "/api/v1/tunnels/by-cxid/$cxid");

                        $all_status_codes[] = $tunnel_deletion->status();
                    }


                    Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification("Prompt to terminate Mercury account received by WHMCS - Tunnels have been removed. STATUS: " . implode(', ', $all_status_codes) . ". NAME: $org->name (OI $org->id) (CXID " . implode(',', $cxids) . ")"));
                    
                    break;
            }

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

        return response()->json(['detail' => 'You need to provide a valid `right` parameter, alongside a valid `organisation_id` slug.'], 400);
        
    }

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

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

        if($valid_request && $_organisation_id != Organisation::where('name', '=', 'Global Secure Layer')->orderBy('id', 'asc')->first()->id) {

            return response()->json(['detail' => ProductRights::where('organisation_id', '=', $_organisation_id)->select('name')->get()->pluck('name')], 200);
        }

        return response()->json(['detail' => 'You need to provide a valid `organisation_id` slug.'], 400);
        
    }

}