<?php

namespace App\Http\Controllers\Goliath;

use App\Http\Controllers\BaseController;
use App\Interfaces\ReportRepositoryInterface;
use App\Models\EmailOrganisationAttributes;
use App\Models\Goliath\ReportLinks;
use App\Models\PendingReport;
use App\Models\PendingReportType;
use App\Models\ReportOrganisationAttribute;
use App\Repositories\ReportRepository;
use App\Rules\AllowedReportIdentity;
use App\Rules\Fragments\PagedTable;
use Throwable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\StreamedResponse;

class ReportController extends BaseController
{
    protected ReportRepositoryInterface $rr;
    protected $admin = false;
    protected $download_passthrough = false;

    public function __construct()
    {
        try {
            $this->rr = new ReportRepository();
        }
        catch(Throwable) {}
        
        parent::__construct();
    }

    public function fetch_from_link(Request $_r, $_link_id) {
       
        $valid_agent = true;

        if(str_contains($_r->userAgent(), 'Slack') === true || str_contains($_r->userAgent(), 'Discord') === true) {
            $valid_agent = false;
        }

        $link_details = ReportLinks::where('token', '=', $_link_id)->first() ?? false;

        $response = false;

        if($link_details && $valid_agent) {
            $report = PendingReport::find($link_details->report_id);

            if($report) {
                $this->download_passthrough = $report->organisation_id;

                switch($report->report_type) {
                    case PendingReportType::Singular->value:
                        $response = $this->report_single($_r, $report->report_id);
                        break;
                    case PendingReportType::Carpet->value:
                        $response = $this->report_carpet($_r, $report->report_id);
                        break;
                    case PendingReportType::Daily->value:
                        $response = $this->report_period($_r, $report->year, PendingReportType::Daily, $report->day);
                        break;
                    case PendingReportType::Weekly->value:
                        $response = $this->report_period($_r, $report->year, PendingReportType::Weekly, $report->week);
                        break;
                    case PendingReportType::Monthly->value:
                        $response = $this->report_period($_r, $report->year, PendingReportType::Monthly, $report->month);
                        break;
                    case PendingReportType::Quarterly->value:
                        $response = $this->report_period($_r, $report->year, PendingReportType::Quarterly, $report->quarter);
                        break;
                }

                $link_details->delete();

                $this->download_passthrough = false;
            }
        }

        if($response !== false) {
            return $response;
        }
        else {
            return Redirect::to(config('creatia.frontend.main'));
        }
    }

    //  Requires a complete re-do
    public function fetch_carpet(Request $_r) {

        /*$validated = $_r->validate([
            'draw' => 'integer',
            'start' => 'required|min:0',
            'length' => 'required|min:0',
            'order_direction' => Rule::in(['asc', 'desc']),
            'order_column' => Rule::in(['destination', 'finish_at', 'start_at', 'gbps', 'mpps']),
            'filter.global' => ['nullable', 'regex:/([\d\.\-\:A-z])/'],
            'filter.column.destination' => ['nullable', 'regex:/([\d\.])/'],
            'filter.column.finish_at' => ['nullable', 'regex:/([\d\-\:])/'],
            'filter.column.start_at' => ['nullable', 'regex:/([\d\-\:])/'],
            'filter.column.organisation' => ['nullable', 'string']
        ]);

        try {
            if($validated) {
                $draw = $_r->input('draw');
                $start = $_r->input('start', 0);
                $length = $_r->input('length', 100);
                $raw_filters = $_r->input('filter', []);
                $order_direction = $_r->input('order_direction', 'DESC');
                $order_column = $_r->input('order_column', 'start_at');

                $filters = [];

                if(isset($raw_filters['global'])) {
                    $filters['global'] = $raw_filters['global'];
                }

                if(isset($raw_filters['column'])) {
                    $filters = array_merge($filters, $raw_filters['column']);
                }

                if(!$this->admin) {
                    $filters['organisation_id'] = Auth::user()->organisation_id;
                }

                $reports = $this->rr->get_list_carpet($filters, $start, $length, $order_column, $order_direction);

                if(!$this->admin) {

                    // We are doing this to ensure that prefixes are only owned by the user - This is indeed incredibly important as while there are many rails in place to keep ownership tracked across reporting and creatia, we should assume they fail.
                    $owned_not_owned = Prefixes::getOrganisationOwnsPrefixes(Auth::user()->organisation_id, array_map(function($x) {
                        return [
                            'address'   => $x['destination'],
                            'time'      => $x['start_at']
                        ];
                    }, $reports['data']->toArray()));
    
                    // Iterate over - Check if failures are subtenanted
                    foreach($reports['data'] as $i => $r) {
                       
                        foreach($owned_not_owned[1] as $not_owned) {
                            
                            $subtenanted = Prefixes::withTrashed()->select('address')
                                ->leftJoin('prefix_subtenants', 'prefix_id', '=', 'prefixes.id')
                                ->where([
                                    [\DB::raw('family(address)'), '=', 4],
                                    ['mask', '>', 0],
                                    ['mask', '<', 33],
                                    ['address', '!=', '0.0.0.0']
                                ])
                                ->whereNotIn(\DB::raw('CONCAT(address, \'/\', mask)'), ['85.154.132.0/2', '223.29.239.0/2', '203.10.98.51/31', '39.38.208.0/2', '198.178.119.8/27'])
                                ->where([
                                    ['subtenant_id', '=', Auth::user()->organisation_id],
                                    [\DB::raw('CONCAT(address, \'/\', mask)::cidr'), '>>', $r['destination']],
                                ])
                                ->count();

                            if($r['destination'] == $not_owned && !$subtenanted) {
                                \Log::debug("ALARM - BAD REPORT DETECTED - DUBIOUS OWNERSHIP DETECTED - HIDING FROM END USER: " . json_encode($r));
    
                                $reports['data'][$i] = [];
                                break;
                            }
    
                        }
                    }

                    $new_rows = [];
                    foreach($reports['data'] as $x) {
                        if(sizeof($x) !== 0) {
                            $new_rows[] = $x;
                        }
                    }

                    $reports['data'] = $new_rows;
                }

                return response()->json([
                    'draw' => (int)($draw ?? 0),
                    'recordsTotal' => $reports['returned'],
                    'recordsFiltered' => $reports['filtered'],
                    'data' => $reports['data']
                ], 200);
            }    
        } catch(\Throwable $ex) {
            \Log::debug($ex);
        }

        return response()->json([
            'draw' => 0,
            'recordsTotal' => 0,
            'recordsFiltered' => 0,
            'data' => []
        ], 200);*/
    }

    public function fetch_single(Request $_r) {

        // We need size rules
        $valid_request = !Validator::make($_r->all(), [
            ...PagedTable::rules(['destination', 'finish_at', 'start_at', 'gbps', 'mpps']),
            'filter.global' => ['nullable', 'regex:/([\d\.\-\:A-Za-z])/'],
            'filter.column.destination' => ['nullable', 'regex:/([\d\.])/'],
            'filter.column.finish_at' => ['nullable', 'regex:/([\d\-\:])/'],
            'filter.column.start_at' => ['nullable', 'regex:/([\d\-\:])/'],
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.422_INVALID_PAYLOAD')], 422);
        }

        if(config('app.demonstration')) {
            
            $demo_data = json_decode(file_get_contents(app_path('Demonstration/ReportListDemo.json')), true);

            return response()->json([
                'draw' => (int)($_r->input('draw')),
                'recordsTotal' => sizeof($demo_data),
                'recordsFiltered' => sizeof($demo_data),
                'data' => $demo_data
            ], 200);
        }

        $this->log_event('ReportController::fetch_single', $_r->input());

        try {
            if($valid_request) {
                $draw = $_r->input('draw');
                $start = $_r->input('start', 0);
                $length = $_r->input('length', 100);
                $raw_filters = $_r->input('filter', []);
                $order_direction = $_r->input('order_direction', 'DESC');
                $order_column = $_r->input('order_column', 'start_at');

                $filters = [
                    'organisation_id' => Auth::user()->organisation_id,
                    ...( isset($raw_filters['global']) ? [ 'global' => $raw_filters['global'] ] : []),
                    ...( isset($raw_filters['column']) ? $raw_filters['column'] : [])
                ];

                $cached = Redis::get('report-singular-' . $filters['organisation_id'] . '-list');

                if($cached !== null && $order_direction === 'DESC' && $order_column === 'start_at' && $start == 0 && $length == 100 && sizeof($filters) === 1) {
                    $this->log_event('ReportController::fetch_single', $_r->input(), 'Cached report list used');

                    return response()->json([ ...json_decode($cached, true), 'draw' => (int)$draw, ], 200);
                }

                $reports = $this->rr->get_list_singular($draw, $filters, $start, $length, $order_column, $order_direction);

                $this->log_event('ReportController::fetch_single', $_r->input(), 'Fresh report list used');

                return response()->json($reports, 200);
            }    
        } catch(\Throwable $ex) {
            $this->log_error('ReportController::fetch_single', $_r->input(), $ex);
        }

        return response()->json([
            'draw' => 0,
            'recordsTotal' => 0,
            'recordsFiltered' => 0,
            'data' => []
        ], 200);
    }

    // Requires a complete re-do
    public function report_carpet(Request $_r, $_report_id) {

        // Passthrough functionality for admins
        /*$organisation_id = 0;

        if($this->download_passthrough === false) {
            if($this->admin === false) {
                $organisation_id = Auth::user()->organisation_id;
            }
        }
        else {
            $organisation_id = $this->download_passthrough;
        }

        // Whether or not to add a watermark
        $watermark = (ReportOrganisationAttribute::where('organisation_id', '=', $organisation_id)->select('watermark')->first() ?? new ReportOrganisationAttribute([
            'watermark' => 0
        ]))->watermark;

        /**
         * Check whether or not the report is owned or leased out. We'll use a few things here. Firstly, we need to check whether or not 
         */

        /*$organisation_subclause = $this->download_passthrough ? ['organisation_id', '!=', 0] : ['organisation_id', '=', $organisation_id];

        $valid_owned = GoliathAttackPrefix::leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')
            ->where([ $organisation_subclause, ['attack_id', '=', $_report_id] ])
            ->orWhere([ $organisation_subclause, [ DB::raw('CONCAT(organisation_id, \'00\', attack_id)'), '=', $_report_id ] ])
            ->select('attack_id')
            ->get()
            ->pluck('attack_id')
            ->toArray();

        $valid_leased = GoliathAttackPrefix::leftJoin('goliath_attacks', 'attack_id', '=', 'goliath_attacks.id')
            ->leftJoin('prefixes', 'goliath_attack_prefixes.prefix_id', '=', 'prefixes.id')
            ->leftJoin('prefix_subtenants', 'prefix_subtenants.prefix_id', '=', 'prefixes.id')
            ->whereRaw('goliath_attacks.started_at >= prefix_subtenants.created_at AND (goliath_attacks.last_at <= prefix_subtenants.deleted_at OR (prefix_subtenants.deleted_at IS NULL))')
            ->where(function($x) use($organisation_id, $_report_id) {
                $x->where([ ['subtenant_id', '=', $organisation_id], ['attack_id', '=', $_report_id] ])->orWhere([ ['subtenant_id', '=', $organisation_id], [ DB::raw('CONCAT(organisation_id, \'00\', attack_id)'), '=', $_report_id ] ]);
            })
            ->select('attack_id')
            ->get()
            ->pluck('attack_id')
            ->toArray();

        /**
         * Run validator - We just get the nice clean assurances of Laravel here.
         */
        /*$validator = Validator::make( [
            'report_id' => $_report_id
        ], [
            'report_id' => $organisation_id == 0 ? 'integer' : Rule::in(array_merge($valid_owned, $valid_leased))
        ]);

        if(!$validator->fails()) {

            /**
             * Retrieve carpet bomb report.
             */

            /*$report = $this->rr->get_report_carpet($_report_id);

            if($report === false) {
                Log::error("Can't get to report");
                return response()->json(['detail' => 'Error processing the request'], 500);
            }
            else {
                try {
                    return new StreamedResponse(function () use ($report) { echo $report; }, 200, [
                        'Content-Type' => 'application/pdf',
                        'Content-Disposition' => "inline; filename=\"Carpet Report $organisation_id-$_report_id.pdf\"",
                    ]);
                }
                catch(\Exception $ex) {
                    Log::error($ex);
                    return response()->json(['detail' => 'Error processing the request'], 500);
                }
            }
        }

        return response()->json(['detail' => 'Unauthorised'], 400);*/
    }

    public function report_single(Request $_r, $_report_id) {

        if(config('app.demonstration')) {

            if($_report_id == 1 || $_report_id == 2) {
                return new StreamedResponse(function () use ($_report_id) {
                    $content = file_get_contents(app_path("Demonstration/report-$_report_id.pdf"));
                    echo $content;
                }, 200, [
                    'Content-Type' => 'application/pdf',
                    'Content-Disposition' => "inline; filename=\"Demonstration Report #$_report_id.pdf\"",
                ]);
            }

            return response()->json(['detail' => 'Error processing the request'], 500);
        }


        // Pull in the organisation ID from the passthrough, or validate that the user has access to the report when it's not a passthrough.
        $organisation_id = null;

        if($this->download_passthrough != false) {

            $this->log_event('ReportController::report_single', $_r->input(), "A download link has been used for $_report_id using Organisation #$this->download_passthrough");

            if($this->admin === false) {
                $organisation_id = $this->download_passthrough;
            }
        }
        else {
            $validator = Validator::make( [
                'report_id' => $_report_id
            ], [
                'report_id' => [ new AllowedReportIdentity ]
            ]);

            if($validator->fails()) {
                return response()->json(['detail' => 'Unauthorised'], 400);
            }

            $organisation_id =  Auth::user()->organisation_id;
        }

        if($organisation_id === null) { // If this happens we've got big problems
            return response()->json(['detail' => 'Unauthorised'], 400);
        }

        // Whether or not to add a watermark
        /*$watermark = (ReportOrganisationAttribute::where('organisation_id', '=', $organisation_id)->select('watermark')->first() ?? new ReportOrganisationAttribute([
            'watermark' => 0
        ]))->watermark;*/

        $this->log_event('ReportController::report_single', $_r->input());
        
        // Check if we have a cached value - If we don't, retrieve it the old fashioned way.
        $cached = Redis::get("report-$organisation_id-$_report_id");

        $report = null;

        if($cached !== null) {
            $report = base64_decode(gzinflate($cached));
        }
        else {
            $report = $this->rr->get_report_singular($_report_id, $organisation_id);
        }

        $this->log_event('ReportController::report_single', $_r->input(), $cached === null ? 'Fresh report downloaded' : 'Cached report downloaded');

        if(!$report) {
            return response()->json(['detail' => 'Error processing the request'], 500);
        }

        // Stream report back
        try {
            return new StreamedResponse(function () use ($report) { echo $report; }, 200, [
                'Content-Type' => 'application/pdf',
                'Content-Disposition' => "attachment; filename=\"DDoS Attack Report $_report_id.pdf\"",
            ]);
        }
        catch(\Throwable $ex) {
            $this->log_error('ReportController::report_single', $_r->input(), $ex);
        }

        return response()->json(['detail' => 'Error processing the request'], 500);
    }

    public function fetch_attributes(Request $_r, $_organisation_id = 0) {
        $_organisation_id = 0;

        if($this->admin === false) {
            $_organisation_id = Auth::user()->organisation_id;
        }
        else {
            $_organisation_id = $_r->input('organisation_id');
        }

        $to_select = [
            'send_individual',
            'send_carpet',
            'send_daily',
            'send_weekly',
            'send_monthly',
            'send_quarterly',
            'minimum_threshold_gbps',
            'minimum_threshold_mpps',
            'send_webhook',
            'send_start',
            'send_finish'
        ];

        $attributes = ReportOrganisationAttribute::where('organisation_id', '=', $_organisation_id)->select($to_select)->first() ?? [
            'send_individual'           => 1,
            'send_carpet'               => 1,
            'send_daily'                => 1,
            'send_weekly'               => 1,
            'send_monthly'              => 1,
            'send_quarterly'            => 1,
            'minimum_threshold_gbps'    => 3,
            'minimum_threshold_mpps'    => 0.1,
            'send_webhook'              => false,
            'send_start'                => true,
            'send_finish'               => true
        ];

        $attributes['emails'] = EmailOrganisationAttributes::where('organisation_id', '=', $_organisation_id)->select('email')->get()->pluck('email');

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

    public function set_attributes(Request $_r, $_organisation_id = 0) {
        $_organisation_id = 0;

        if($this->admin === false) {
            $_organisation_id = Auth::user()->organisation_id;
        }
        else {
            $_organisation_id = $_r->input('organisation_id');
        }

        $validator = Validator::make($_r->input(), array_merge([
            'send_individual'           => ['required', 'boolean'],
            'send_carpet'               => ['required', 'boolean'],
            //'send_daily'                => ['required', 'boolean'],
            //'send_weekly'               => ['required', 'boolean'],
            //'send_monthly'              => ['required', 'boolean'],
            //'send_quarterly'            => ['required', 'boolean'],
            'send_webhook'              => ['required', 'boolean'],
            'send_start'                => ['required', 'boolean'],
            'send_finish'               => ['required', 'boolean'],
            'minimum_threshold_gbps'    => ['required', 'integer', 'min:1'],
            //'minimum_threshold_mpps'    => ['required', 'numeric', 'min:0.1'],
            //'emails.*'                  => ['email:rfc,dns']
        ], $this->admin ? [

        ] : []));

        \Log::debug($validator->errors());

        if(!$validator->fails()) {

            $attributes = ReportOrganisationAttribute::where('organisation_id', '=', $_organisation_id)->first() ?? new ReportOrganisationAttribute([
                'organisation_id' => $_organisation_id
            ]);

            $attributes->send_individual        = $_r->input('send_individual');
            $attributes->send_carpet            = $_r->input('send_carpet');
            $attributes->send_daily             = false; //$_r->input('send_daily');
            $attributes->send_weekly            = false; //$_r->input('send_weekly');
            $attributes->send_monthly           = false; //$_r->input('send_monthly');
            $attributes->send_quarterly         = false; //$_r->input('send_quarterly');
            $attributes->send_webhook           = $_r->input('send_webhook');
            $attributes->send_start             = $_r->input('send_start');
            $attributes->send_finish            = $_r->input('send_finish');
            $attributes->minimum_threshold_gbps = $_r->input('minimum_threshold_gbps');
            $attributes->minimum_threshold_mpps = 0.1; //$_r->input('minimum_threshold_mpps');

            /*// Clear out the old emails and insert the new ones...
            EmailOrganisationAttributes::where('organisation_id', '=', $_organisation_id)->delete();

            $email_inserts = [];

            foreach($_r->input('emails') as $x) {
                $email_inserts[] = [
                    'organisation_id'   => $_organisation_id,
                    'email'             => $x
                ];
            }

            if($email_inserts !== []) {
                EmailOrganisationAttributes::insert($email_inserts);
            }*/

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

            return response()->json(['detail' => 'Error processing the request'], 500);
        }

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