<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\AdminLoginRequest;
use App\Http\Requests\Auth\AdminLoginOtpRequest;
use App\Http\Requests\Auth\AdminVerifyOtpRequest;
use App\Http\Controllers\JsonApiAuth\Actions\AuthKit;
use App\Http\Controllers\JsonApiAuth\Revokers\PassportRevoker;
use App\Http\Controllers\JsonApiAuth\Revokers\RevokerFactory;
use App\Http\Controllers\JsonApiAuth\Revokers\SanctumRevoker;
use App\Models\Admin;
use App\Models\AdminLoginOtp;
use App\Models\DeviceBrowser;
use App\Models\DeviceOperativeSystem;
use App\Models\DeviceType;
use App\Models\Persona;
use App\Models\TokenDevice;
use App\Notifications\AdminLoginOtpNotification;
use App\Services\PersonaService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class AuthController extends Controller
{
    public function adminLogin(AdminLoginRequest $request, PersonaService $personaService)
    {
        $credentials = $request->only('email', 'password');


        $admin = $this->getAdminForAuth($request->get('email'));

        if (!Auth::guard('admin')->attempt($credentials) || !$admin || !$admin->estado) {
            return response()->json([
                'errors' => [[
                    'status' => (string) 401,
                    'source' => (object) [],
                    'title' => 'Acceso inválido',
                    'detail' => 'Credenciales inválidas',
                ]]
            ], 401);
        }

        $device = (object) request('device');

        return $this->createAdminAuthResponse($admin, $device, $personaService);
    }

    public function requestAdminOtp(AdminLoginOtpRequest $request): \Illuminate\Http\JsonResponse
    {
        $admin = $request->getAdmin();
        $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);

        AdminLoginOtp::query()
            ->where('admin_id', $admin->id)
            ->delete();

        AdminLoginOtp::create([
            'admin_id' => $admin->id,
            'code' => Hash::make($code),
            'expires_at' => now()->addMinutes(10),
        ]);

        $admin->notify(new AdminLoginOtpNotification($code));

        return response()->json([
            'message' => 'Te enviamos un código OTP de 6 dígitos a tu correo.',
        ]);
    }

    public function verifyAdminOtp(AdminVerifyOtpRequest $request, PersonaService $personaService)
    {
        $admin = $request->getAdmin();
        $otp = $request->getOtpFor($admin);

        $otp->update([
            'used_at' => now(),
        ]);

        $device = (object) $request->get('device');

        return $this->createAdminAuthResponse($admin, $device, $personaService);
    }

    private function response(string $token, string $uuid, $admin, $userType = 2)
    {
        return response()->json([
            'headers' => [
                'acess_token' => $token,
                'token_type' => 'Bearer',
                'accept' => 'application/vnd.api+json',
                'uuid' => $uuid,
            ],
            'details' => [
                'user_type' => $userType,
                'data' => $admin
            ]
        ], 200);
    }

    public function devicesList(Request $request)
    {
        $user_id = $request->user()->id;


        $token = $request->header('authorization');
        $data = $token ? explode('|', $token) : [];

        $token_id = sizeof($data) ? ($data[0]) : null;
        $token_id = intval(preg_replace('/[^0-9]+/', '', $token_id), 10);

        if (!is_numeric($token_id))
            return [];

        $result = TokenDevice::from('token_devices as td')
            ->join('personal_access_tokens as pat', 'pat.id', 'td.personal_access_token_id')
            ->leftJoin('devices as d', 'd.id', 'td.device_id')
            ->leftJoin('devices_operative_systems as dos', 'dos.id', 'td.device_operative_system_id')
            ->leftJoin('browsers as b', 'b.id', 'td.browser_id')
            ->where('name', 'admin_token')
            ->where('tokenable_id', $user_id)
            ->select(
                'pat.id',
                'td.uuid',
                'td.device_id',
                'd.slug as device_slug',
                'd.titulo as device_titulo',
                'td.device_operative_system_id',
                'dos.titulo as device_operative_system_titulo',
                'dos.platform',
                'td.browser_id',
                'b.titulo as browser_titulo',
                DB::raw('if(pat.id=' . $token_id . ',1,0) as utilizado'),
                'td.created_at'
            )
            ->orderByRaw('pat.id=' . $token_id . ' desc')
            ->orderBy('pat.id', 'desc')
            ->get();

        return $result;
    }

    public function logoutOtherDevice(Request $request)
    {
        if (!$request->id)
            abort(404);

        DB::table('personal_access_tokens')
            ->where('id',  $request->id)
            ->delete();
    }

    public function logoutAllDevice(Request $request)
    {
        $token = $request->header('authorization');
        $data = $token ? explode('|', $token) : [];

        $token_id = sizeof($data) ? ($data[0]) : null;
        $token_id = intval(preg_replace('/[^0-9]+/', '', $token_id), 10);

        if (!is_numeric($token_id))
            return [];

        DB::table('personal_access_tokens')
            ->where('id', '<>', $token_id)
            ->delete();
    }

    public function logout(Request $request)
    {
        if (AuthKit::isSanctum()) {
            if ($request->user()->currentAccessToken()->delete()) {
                return response()->json([
                    'message' => 'Sessión cerrada'
                ], 200);
            }
        }
        return response()->json(['errors' => [[
            'status' => (string) 401,
            'source' => (object) [],
            'title' => 'Error al tratar de cerrar sesión',
            'detail' => 'Intente de nuevo más tarde',
        ]]], 422);
    }



    //TODO :: FUNCIONES PRIVADAS

    private function getAdminForAuth(string $email): ?Admin
    {
        return Admin::query()
            ->where('email', $email)
            ->with(['persona'])
            ->select(
                'id',
                'username',
                'email',
                'root',
                'email_verified_at',
                'persona_id',
                'estado'
            )
            ->first();
    }

    private function createAdminAuthResponse(Admin $admin, object $device, PersonaService $personaService)
    {
        $token = $admin->createToken('admin_token')->plainTextToken;

        $this->deviceRegister($token, $device);

        $admin->persona->imageUrl = $personaService
            ->setPersona($admin->persona)
            ->getImageUrl('small');

        return $this->response($token, $device->uuid, $admin, $admin->root ? 2 : 1);
    }

    private function deviceRegister(String $token = '', Object $device = null)
    {
        if (!$device || !$device->uuid) return;

        $os = (object) $device->os;
        $os = [
            'titulo' => $os->name,
            'platform' => $os->platform,
            'slug' => Str::slug($os->name . ' ' . $os->platform)
        ];

        $os = DeviceOperativeSystem::createOrGet($os);


        $client = (object) $device->client;
        $browser = array_merge((array) $client, [
            'titulo' => $client->name,
            'slug' => Str::slug($client->name)
        ]);

        $browser = DeviceBrowser::createOrGet($browser);


        $device_input = [
            'titulo' => $device->type,
            'slug' => Str::slug($device->type)
        ];

        $device_input = DeviceType::createOrGet($device_input);

        $data = $token ? explode('|', $token) : [];
        $token_id = sizeof($data) ? (int)($data[0]) : null;

        if (!$token_id) return;

        $device_token = [
            'uuid' => $device->uuid,
            'personal_access_token_id' => $token_id,
            'device_id' => $device_input->id,
            'device_operative_system_id' => $os->id,
            'browser_id' => $browser->id
        ];

        TokenDevice::create($device_token);
    }
}
