<?php

namespace App\Http\Controllers;

use App\Models\District;
use Maatwebsite\Excel\Facades\Excel;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use App\Models\User;

use App\Models\UserRole;
use App\Models\Location;
use App\Models\Partner;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Cache;
use App\Services\AccessControl\PermissionRegistry;
use App\Models\RolePermission;
use App\Services\AccessControl\DataScopeService;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Schema;

class UserMgtController extends Controller
{



    function index(){
        $user = Auth::user();
        $scope = app(DataScopeService::class);
        $roles = UserRole::all();

        if ($scope->userHasNationalAccess($user)) {
            $district = District::all();
            $partners = Partner::all();
        } else {
            $districtIds = $scope->districtIdsFor($user);
            $partnerIds = $scope->partnerIdsFor($user);
            $district = !empty($districtIds) ? District::whereIn('id', $districtIds)->get() : collect();
            $partners = !empty($partnerIds) ? Partner::whereIn('id', $partnerIds)->get() : collect();
        }

    	return view('users.index')->with(['roles'=>$roles, 'districts'=>$district, 'partners'=>$partners]);
    }

    function changestatus(Request $request, $userid, $status){
        $formdata['userid'] = decrypt($userid); $formdata['status'] = decrypt($status);
        $now = date_create('now', timezone_open('Africa/Kampala'))->format('Y-m-d H:i:s');
        $loggedinuser = Auth::user()->id;
        $user = new User();
        $update = $user->changestatus($formdata, $now, $loggedinuser);
        if(isset($update['success_message'])){
            return back()->with('message', $update['success_message']);
        }else{
            return back()->with('error_message', $update['error_message']);
        }
        //return $loggedinuser;
    }

    function partners(){
        $user = Auth::user();
        $usrditrct = Auth::user()->district;
        $roles = UserRole::where('id', 5)->get();
        $district = Location::where('locationtype', 2)->where('id', Auth::user()->district)->get();
        if(Empty($usrditrct) || $usrditrct=="*"){
            $district = Location::where('locationtype', 2)->get();
        }
        $partners = Partner::where('id', Auth::user()->partnerid)->get();
        if(Auth::user()->partner != 1){
            $partners = Partner::all();
        }
        return view('users.partners')->with(['roles'=>$roles, 'districts'=>$district, 'partners'=>$partners]);
    }

    function userroles(){
        $roles = UserRole::orderBy('name')->get();
        $permissionRegistry = app(\App\Services\AccessControl\PermissionRegistry::class);
        return view('roles.list')->with(['roles' => $roles, 'permissionRegistry' => $permissionRegistry]);
    }

    public function configurePermissions($roleKey){
        $role = $this->resolveRoleByPublicKey($roleKey);
        $permissionGroups = PermissionRegistry::modules();
        $assignedPermissions = RolePermission::where('role_id', $role->id)
            ->where('is_allowed', 1)
            ->get()
            ->map(function ($row) { return $row->module . '.' . $row->permission; })
            ->values()
            ->all();

        return view('roles.permissions')->with([
            'role' => $role,
            'permissionGroups' => $permissionGroups,
            'assignedPermissions' => $assignedPermissions,
        ]);
    }

    public function saveRolePermissions(Request $request){
        $request->validate([
            'role_id' => 'required',
            'permissions' => 'array',
        ]);

        $role = $this->resolveRoleByPublicKey($request->input('role_id'));
        return $this->persistRolePermissions($role->id, $request->input('permissions', []));
    }

    public function saveRolePermissionsForRole(Request $request, $roleKey){
        $request->validate([
            'permissions' => 'array',
        ]);

        $role = $this->resolveRoleByPublicKey($roleKey);
        return $this->persistRolePermissions($role->id, $request->input('permissions', []));
    }

    private function persistRolePermissions($roleId, array $permissions){
        DB::transaction(function () use ($roleId, $permissions) {
            RolePermission::where('role_id', $roleId)->delete();

            foreach ($permissions as $permission) {
                if (strpos($permission, '.') === false) {
                    continue;
                }
                [$module, $action] = explode('.', $permission, 2);
                RolePermission::create([
                    'role_id' => $roleId,
                    'module' => $module,
                    'permission' => $action,
                    'is_allowed' => true,
                    'created_by' => Auth::id(),
                    'updated_by' => Auth::id(),
                ]);
            }
        });

        PermissionRegistry::clearRoleCache($roleId);

        return redirect('/roles/list')->with('message', 'Role permissions updated successfully.');
    }

    public function roles(Request $request)
    {
        $validated = $request->validate([
            'id' => ['nullable', 'string', 'max:255'],
            'uuid' => ['nullable', 'string', 'max:255'],
        ]);

        $role = null;
        $roleKey = $validated['id'] ?? $validated['uuid'] ?? null;

        if (!empty($roleKey)) {
            $role = $this->resolveRoleByPublicKey($roleKey);
        }

        return view('roles.index')->with(['role' => $role]);
    }

    public function saveRole(Request $request)
    {
        $validated = $request->validate([
            'id' => 'nullable|string',
            'uuid' => 'nullable|string',
            'role' => 'required|string|max:150',
            'is_viewonly' => 'nullable|boolean',
        ]);

        $role = null;
        $roleKey = $validated['uuid'] ?? $validated['id'] ?? null;
        if (!empty($roleKey)) {
            $role = $this->resolveRoleByPublicKey($roleKey);
        }

        $duplicateQuery = UserRole::where('name', trim($validated['role']));
        if ($role) {
            $duplicateQuery->where('id', '!=', $role->id);
        }
        if ($duplicateQuery->exists()) {
            return back()->withInput()->with('error_message', 'A role named '.$validated['role'].' already exists.');
        }

        DB::beginTransaction();
        try {
            $now = now();
            if (!$role) {
                $role = new UserRole();
                if (empty($role->uuid) && Schema::hasColumn($role->getTable(), 'uuid')) {
                    $role->uuid = (string) Str::uuid();
                }
                $role->created_by = Auth::id();
                $role->created_at = $now;
            }

            $role->name = trim($validated['role']);
            $role->editable = $request->input('editable', '1');
            $role->is_viewonly = $request->boolean('is_viewonly') ? 1 : 0;
            $role->updated_by = Auth::id();
            $role->updated_at = $now;
            $role->saveOrFail();

            DB::commit();

            return redirect('/roles/list')->with('message', 'User role saved successfully.');
        } catch (\Throwable $exception) {
            DB::rollBack();
            \Log::error('Failed to save user role', [
                'error' => $exception->getMessage(),
                'user_id' => Auth::id(),
                'role_key' => $roleKey,
            ]);
            return back()->withInput()->with('error_message', 'The role could not be saved. Please try again or contact the system administrator.');
        }
    }

    private function resolveRoleByPublicKey($key): UserRole
    {
        $query = UserRole::query();
        if (Schema::hasColumn((new UserRole())->getTable(), 'uuid')) {
            $query->where('uuid', $key);
        }
        if (is_numeric($key)) {
            $query->orWhere('id', $key);
        }
        return $query->firstOrFail();
    }

    function viewusers(){
        $query = DB::table('users')
        ->leftjoin('user_roles', 'users.userrole', '=', 'user_roles.id')
        ->leftjoin('districts', DB::raw("FIND_IN_SET(districts.id, users.district)"),">",DB::raw("'0'"))
        ->select('users.*', 'user_roles.name as userrole'  , DB::raw("GROUP_CONCAT(districts.name) as districtname") )
        ->groupBy("users.id")
        ->orderBy('users.name');

        app(DataScopeService::class)->applyToUserQuery($query, Auth::user(), 'users');
        $users = $query->get();
        return view('users.list')->with(['users'=>$users]);
    }

    protected function validator(Request $request)
    {
        $formvalues = request();

        return Validator::make($formvalues, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }

    /**
     Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\Models\User
     */

    protected function processuser(Request $request)
    {
        $id = $request->input('id');

        $validated = $request->validate([
            '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', \Illuminate\Validation\Rule::unique('users', 'email')->ignore($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' => [$id ? 'nullable' : 'required', 'string', 'min:8', 'max:255'],
            'password_confirmation' => ['nullable', 'same:password'],
            'password_confirmatio' => ['nullable', 'same:password'],
        ]);

        $district = '';
        if (!empty($validated['district'])) {
            $district = implode(',', array_map('intval', $validated['district']));
        }

        try {
            app(DataScopeService::class)->assertCanAssignDistricts(Auth::user(), $validated['district'] ?? []);
        } catch (ValidationException $e) {
            return back()->withErrors($e->errors())->withInput();
        }

        $partner = '';
        $partnerid = null;
        if (!empty($validated['partner']) && empty($validated['district'])) {
            $partner = $validated['partner'];
            $partnerid = $validated['partnerid'] ?? $validated['partner'];

            $partnerRecord = Partner::select('districts')->find($partnerid);
            if ($partnerRecord) {
                $district = $partnerRecord->districts;
            }
        }

        try {
            app(DataScopeService::class)->assertCanAssignPartner(Auth::user(), $partnerid ?: ($validated['partnerid'] ?? null));
        } catch (ValidationException $e) {
            return back()->withErrors($e->errors())->withInput();
        }

        $payload = [
            'name' => $validated['name'],
            'username' => $validated['username'] ?? $validated['email'],
            'email' => strtolower($validated['email']),
            'district' => $district,
            'partner' => $partner,
            'partnerid' => $partnerid,
            'userrole' => $validated['accesslevel'],
        ];

        if (!empty($validated['password'])) {
            $payload['password'] = Hash::make($validated['password']);
        }

        User::updateOrCreate(['id' => $id ?: null], $payload);

        return back()->with('message', 'A user account for '.$validated['name'].' has been saved.');
    }

    public function createparner(Request $request)
    {
        return $this->createpartner($request);
    }

    protected function createpartner(Request $request)
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:180'],
            'username' => ['nullable', 'string', 'max:180', 'regex:/^[A-Za-z0-9_\-\.@ ]+$/'],
            'email' => ['required', 'email', 'max:180', 'unique:users,email'],
            'district' => ['nullable', 'array'],
            'district.*' => ['integer', 'exists:districts,id'],
            'partner' => ['required', 'integer', 'exists:partners,id'],
            'accesslevel' => ['required', 'integer', 'exists:user_roles,id'],
            'password' => ['required', 'string', 'min:8', 'max:255'],
            'password_confirmation' => ['required', 'same:password'],
        ]);

        $district = '';
        if (!empty($validated['district'])) {
            $district = implode(',', array_map('intval', $validated['district']));
        }

        try {
            app(DataScopeService::class)->assertCanAssignDistricts(Auth::user(), $validated['district'] ?? []);
            app(DataScopeService::class)->assertCanAssignPartner(Auth::user(), $validated['partner']);
        } catch (ValidationException $e) {
            return back()->withErrors($e->errors())->withInput();
        }

        User::create([
            'name' => $validated['name'],
            'username' => $validated['username'] ?? $validated['email'],
            'email' => strtolower($validated['email']),
            'district' => $district,
            'partnerid' => $validated['partner'],
            'userrole' => $validated['accesslevel'],
            'password' => Hash::make($validated['password']),
        ]);

        return back()->with('message', 'A user account for '.$validated['name'].' has been created.');
    }

    public function updatepassword(Request $request){
        $validated = $request->validate([
            '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'],
        ]);

        $userid = base64_decode($validated['userid'], true);
        abort_unless($userid && ctype_digit((string) $userid), 422, 'Invalid user reference.');

        $users = User::findOrFail((int) $userid);
        $users->password = Hash::make($validated['password']);

        if($users->save()){
            return back()->with('message', 'Password update has been completed successfully.');
        }

        return back()->with('error_message', 'Password update failed.');
    }
}
