<?php

namespace App\Http\Middleware;

use App\Services\Security\SecurityEventLogger;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class BusinessInputValidation
{
    private $logger;

    public function __construct(SecurityEventLogger $logger)
    {
        $this->logger = $logger;
    }

    public function handle(Request $request, Closure $next)
    {
        $route = $request->route();
        if (!$route) {
            return $next($request);
        }

        $action = $route->getActionName();
        $method = null;
        $controller = null;

        if (is_string($action) && strpos($action, '@') !== false) {
            [$controller, $method] = explode('@', $action, 2);
        }

        if (!$controller || !$method) {
            return $next($request);
        }

        $rules = $this->rulesFor($controller, $method, $request);

        if (empty($rules)) {
            return $next($request);
        }

        $data = array_merge($request->route()->parameters(), $request->all());

        $validator = Validator::make($data, $rules, $this->messages());

        if ($validator->fails()) {
            $this->logger->log('business_validation_failed', 'warning', [
                'controller' => $controller,
                'method' => $method,
                'fields' => array_keys($validator->errors()->toArray()),
            ], $request);

            if ($request->expectsJson() || $request->ajax() || $request->is('api/*')) {
                return response()->json([
                    'success' => false,
                    'message' => 'The submitted data is invalid.',
                    'errors' => $validator->errors(),
                ], 422);
            }

            return back()
                ->withErrors($validator)
                ->withInput($request->except([
                    'password',
                    'password_confirmation',
                    'password_confirmatio',
                    'cpassword',
                    'current_password',
                ]))
                ->with('error_message', 'Some mandatory fields are missing or invalid. Please review the highlighted fields and try again.');
        }

        return $next($request);
    }

    private function rulesFor(string $controller, string $method, Request $request): array
    {
        $name = ltrim($controller, '\\');

        switch ($name) {
            case 'App\Http\Controllers\ConfigurationController':
                return $this->configurationRules($method);

            case 'App\Http\Controllers\DataEntryController':
                return $this->dataEntryRules($method);

            case 'App\Http\Controllers\DownloadController':
                return $this->downloadRules($method);

            case 'App\Http\Controllers\DropdownController':
                return $this->dropdownRules($method);

            case 'App\Http\Controllers\IndexController':
                return $this->indexRules($method);

            case 'App\Http\Controllers\IndicatorController':
                return $this->indicatorRules($method);

            case 'App\Http\Controllers\LocationController':
                return $this->locationRules($method);

            case 'App\Http\Controllers\PartnerController':
                return $this->partnerRules($method);

            case 'App\Http\Controllers\SettingsController':
                return $this->settingsRules($method);

            case 'App\Http\Controllers\UserMgtController':
                return $this->userRules($method, $request);
        }

        return [];
    }

    private function configurationRules(string $method): array
    {
        if ($method !== 'savekey') {
            return [];
        }

        return [
            'resource' => ['required', 'string', 'max:150'],
            'key' => ['required', 'string', 'max:255', 'regex:/^[A-Za-z0-9\-\_\.\:]+$/'],
            'start_date' => ['required', 'date_format:Y-m-d'],
            'end_date' => ['required', 'date_format:Y-m-d', 'after_or_equal:start_date'],
        ];
    }

    private function dataEntryRules(string $method): array
    {
        if (in_array($method, ['list', 'profiledcases'], true)) {
            return [
                'start_date' => ['nullable', 'date'],
                'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
                'perPage' => ['nullable', 'integer', 'min:1', 'max:5000'],
                'page' => ['nullable', 'integer', 'min:1', 'max:100000'],
            ];
        }

        if ($method === 'processOfflineImport') {
            return [
                'offline_file' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:10240'],
                'mode' => ['nullable', Rule::in(['validate_only', 'import'])],
            ];
        }

        if ($method === 'caseReport') {
            return [
                'id' => ['required', 'string', 'max:255'],
            ];
        }

        return [];
    }

    private function downloadRules(string $method): array
    {
        if (!in_array($method, ['exportToExcel', 'excel'], true)) {
            return [];
        }

        return [
            'csv_text' => ['required', 'string', 'max:2000000'],
            'nodecode' => ['nullable', 'boolean'],
            'reporttitle' => ['nullable', 'string', 'max:180'],
        ];
    }

    private function dropdownRules(string $method): array
    {
        $commonId = ['nullable', 'integer', 'min:1'];

        switch ($method) {
            case 'findSubcounty':
                return [
                    'id' => $commonId,
                    'district_id' => $commonId,
                    'districtid' => $commonId,
                ];

            case 'findParish':
                return [
                    'id' => $commonId,
                    'subcounty_id' => $commonId,
                    'subcountyid' => $commonId,
                ];

            case 'findVillage':
                return [
                    'id' => $commonId,
                    'parish_id' => $commonId,
                    'parishid' => $commonId,
                ];

            case 'findNSCategory':
                return [
                    'id' => $commonId,
                    'national_status_id' => $commonId,
                    'national_status' => $commonId,
                    'nstatus' => $commonId,
                ];
        }

        return [];
    }

    private function indexRules(string $method): array
    {
        if ($method !== 'mapdata') {
            return [];
        }

        return [
            'district_id' => ['nullable', 'integer', 'min:1', 'exists:districts,id'],
        ];
    }

    private function indicatorRules(string $method): array
    {
        $idRules = [
            'id' => ['required', 'integer', 'min:1'],
        ];

        $nameRules = [
            'name' => ['required', 'string', 'max:180'],
            'notes' => ['nullable', 'string', 'max:3000'],
        ];

        $incidentRules = [
            'name' => ['required', 'string', 'max:180'],
            'code' => ['nullable', 'string', 'max:60', 'regex:/^[A-Za-z0-9\-_\.\/ ]+$/'],
            'notes' => ['nullable', 'string', 'max:3000'],
        ];

        $ageRules = [
            'name' => ['required', 'string', 'max:120'],
            'min' => ['required', 'integer', 'min:0', 'max:150'],
            'max' => ['required', 'integer', 'min:0', 'max:150', 'gte:min'],
        ];

        $createOnlyName = [
            'name' => ['required', 'string', 'max:180'],
        ];

        $map = [
            'processincident' => $incidentRules,
            'updateincident' => array_merge($idRules, $incidentRules),
            'processage' => $ageRules,
            'updateagegroups' => array_merge($idRules, $ageRules),

            'processgender' => $createOnlyName,
            'updategender' => array_merge($idRules, $createOnlyName),

            'processreligion' => $nameRules,
            'updatereligion' => array_merge($idRules, $nameRules),
            'processrelations' => $nameRules,
            'updaterelations' => array_merge($idRules, $nameRules),
            'processtimeofday' => $nameRules,
            'updatetimeofday' => array_merge($idRules, $nameRules),

            'processmaritalstatus' => $createOnlyName,
            'updatemaritalstatus' => array_merge($idRules, $createOnlyName),
            'processnationalstatus' => $createOnlyName,
            'updatenationalstatus' => array_merge($idRules, $createOnlyName),
            'processdisability' => $createOnlyName,
            'updatedisability' => array_merge($idRules, $createOnlyName),

            'disableage' => $idRules,
            'disablereligion' => $idRules,
            'disablerelations' => $idRules,
            'disabletime' => $idRules,
            'disablemariage' => $idRules,
            'disablenationalstatus' => $idRules,
            'disabledisabiliy' => $idRules,
        ];

        return $map[$method] ?? [];
    }

    private function locationRules(string $method): array
    {
        if (in_array($method, ['nationality', 'region', 'district', 'subcounty', 'parish'], true)) {
            return [
                'id' => ['nullable', 'string', 'max:255'],
            ];
        }

        if ($method === 'postnationality') {
            return [
                'id' => ['nullable', 'string', 'max:255'],
                'name' => ['required', 'string', 'max:150'],
                'code' => ['nullable', 'string', 'max:50'],
                'notes' => ['nullable', 'string', 'max:3000'],
            ];
        }

        if (in_array($method, ['storeDistrict', 'updateDistrict'], true)) {
            return [
                'id' => ['nullable', 'string', 'max:255'],
                'name' => ['required', 'string', 'max:150'],
                'code' => ['nullable', 'string', 'max:50'],
                'regions_id' => ['required', 'integer', 'exists:regions,id'],
                'latitude' => ['nullable', 'numeric', 'between:-90,90'],
                'latitudes' => ['nullable', 'numeric', 'between:-90,90'],
                'longitude' => ['nullable', 'numeric', 'between:-180,180'],
                'longitudes' => ['nullable', 'numeric', 'between:-180,180'],
                'notes' => ['nullable', 'string', 'max:3000'],
            ];
        }

        if (in_array($method, ['storeParish', 'updateParish'], true)) {
            return [
                'id' => ['nullable', 'string', 'max:255'],
                'name' => ['required', 'string', 'max:150'],
                'districts_id' => ['required', 'integer', 'exists:districts,id'],
                'sub_counties_id' => ['required', 'integer', 'exists:sub_counties,id'],
                'regions_id' => ['nullable', 'integer', 'exists:regions,id'],
            ];
        }

        return [];
    }

    private function partnerRules(string $method): array
    {
        if (in_array($method, ['process', 'update'], true)) {
            $rules = [
                'name' => ['required', 'string', 'max:180'],
                'contactperson' => ['nullable', 'string', 'max:180'],
                'contact' => ['nullable', 'string', 'max:80'],
                'location' => ['nullable', 'string', 'max:500'],
                'districtids' => ['nullable', 'array'],
                'districtids.*' => ['integer', 'exists:districts,id'],
            ];

            if ($method === 'update') {
                $rules['id'] = ['required', 'integer', 'exists:partners,id'];
            }

            return $rules;
        }

        if (in_array($method, ['processcategory', 'updatecategory'], true)) {
            $rules = [
                'name' => ['required', 'string', 'max:150'],
            ];

            if ($method === 'updatecategory') {
                $rules['id'] = ['required', 'integer', 'exists:partner_categories,id'];
            }

            return $rules;
        }

        if ($method === 'process_service') {
            return [
                'id' => ['nullable', 'string', 'max:255'],
                'name' => ['required', 'string', 'max:180'],
                'status' => ['nullable', 'integer', Rule::in([0, 1])],
            ];
        }

        return [];
    }

    private function settingsRules(string $method): array
    {
        if ($method !== 'saveTheme') {
            return [];
        }

        return [
            'theme_template' => ['nullable', 'string', 'max:60'],
            'primary' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
            'sidebar' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
            'topbar' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
            'background' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
            'surface' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
        ];
    }

    private function userRules(string $method, Request $request): array
    {
        if ($method === 'roles') {
            return [
                'id' => ['nullable', 'string', 'max:255'],
                'uuid' => ['nullable', 'string', 'max:255'],
            ];
        }

        if (in_array($method, ['processuser', 'createpartner', 'createparner'], true)) {
            $isUpdate = $request->filled('id');

            return [
                'id' => ['nullable', 'integer', 'exists:users,id'],
                'name' => ['required', 'string', 'max:180'],
                'username' => ['nullable', 'string', 'max:180', 'regex:/^[A-Za-z0-9_\-\.@ ]+$/'],
                'email' => [
                    'required',
                    'email',
                    'max:180',
                    Rule::unique('users', 'email')->ignore($request->input('id')),
                ],
                'district' => ['nullable', 'array'],
                'district.*' => ['integer', 'exists:districts,id'],
                'partner' => ['nullable'],
                'partnerid' => ['nullable', 'integer', 'exists:partners,id'],
                'accesslevel' => ['required', 'integer', 'exists:user_roles,id'],
                'password' => [$isUpdate ? 'nullable' : 'required', 'string', 'min:8', 'max:255'],
                'password_confirmation' => ['nullable', 'same:password'],
                'password_confirmatio' => ['nullable', 'same:password'],
            ];
        }

        if ($method === 'updatepassword') {
            return [
                'userid' => ['required', 'string', 'max:255'],
                'email' => ['nullable', 'email', 'max:180'],
                'cpassword' => ['required', 'string', 'min:8', 'max:255'],
                'password' => ['required', 'string', 'min:8', 'max:255', 'same:cpassword'],
            ];
        }

        return [];
    }

    private function messages(): array
    {
        return [
            'required' => 'The :attribute field is required.',
            'email' => 'The :attribute must be a valid email address.',
            'integer' => 'The :attribute must be a valid number.',
            'exists' => 'The selected :attribute is not valid.',
            'max' => 'The :attribute is too long.',
            'regex' => 'The :attribute format is invalid.',
            'same' => 'The :attribute does not match.',
            'after_or_equal' => 'The :attribute must be after or equal to the start date.',
            'gte' => 'The maximum value must be greater than or equal to the minimum value.',
        ];
    }
}
