<?php

namespace App\Http\Controllers\API\v1\Voter;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Util;
use App\UserOTP;
use App\Voter;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Auth;
use GuzzleHttp\Client;

class LoginController extends Controller
{
    public function __invoke(Request $request)
    {
        /* Request OTP (or lookup phone only when send_otp is false) */
        try {
            // $voter = Voter::where('no_kp', $request->no_kp)->firstOrFail();

            $voter = $this->findVoterForCurrentElection($request->no_kp);

            if (! $this->wantsSendOtp($request)) {
                activity()
                    ->performedOn($voter)
                    ->withProperties([
                        'election_id' => Util::getCurrentElection(),
                        'voter_id' => $voter->id,
                        'voter_name' => $voter->name,
                        'no_kp' => $voter->no_kp,
                        'telefon' => $voter->telefon,
                        'ip' => $request->ip(),
                        'user_agent' => $request->userAgent(),
                    ])
                    ->log('voter lookup phone (no tac sent)');

                return response()->json([
                    'status' => 'success',
                    'message' => 'Nombor telefon dijumpai.',
                    'notel' => $voter->telefon,
                    'nokp' => $voter->no_kp,
                ]);
            }

            $currentDateTime = Carbon::now();

            $expired_at = Carbon::now()->addMinutes(config('onewaysms.minutes'));
            $created_at = $currentDateTime->toDateTimeString();

            if ($voter) {
                $expiration = $this->checkExpirationTAC($request->no_kp);

                if ($expiration['notexpired'] == true) {
                    throw new \Exception('Expired TAC : ' . $expiration['remaining']);
                }

                $sms_token = $this->getTokenSMS($voter->telefon, $currentDateTime);

                if ($sms_token == 'error') {
                    throw new \Exception('error in getting the data from SMSToken');
                }

                $user_otp = new UserOTP();
                $user_otp->nokp = $voter->no_kp;
                $user_otp->telefon = $voter->telefon;
                $user_otp->token = $sms_token;
                $user_otp->created_at = $created_at;
                $user_otp->expired_at = $expired_at;
                $user_otp->save();

                activity()
                    ->performedOn($voter)
                    ->withProperties([
                        'election_id' => Util::getCurrentElection(),
                        'voter_id' => $voter->id,
                        'voter_name' => $voter->name,
                        'no_kp' => $voter->no_kp,
                        'telefon' => $voter->telefon,
                        'otp_expires_at' => $expired_at->toDateTimeString(),
                        'ip' => $request->ip(),
                        'user_agent' => $request->userAgent(),
                    ])
                    ->log('voter request tac');

                $response = [
                    'status' => 'success',
                    'message' => 'generated SMS Token',
                    'notel' => $voter->telefon,
                    'nokp' => $voter->no_kp,
                ];

                if (app()->environment(['local', 'development'])) {
                    $response['debug_otp'] = $sms_token;
                }

                return response()->json($response);
            }

            return response()->json([
                'status' => 'failed',
                'message' => 'Does not found NO KP.',
            ]);
        } catch (ModelNotFoundException $e) {
            activity()
                ->withProperties([
                    'no_kp' => (string) $request->input('no_kp'),
                    'election_id' => Util::getCurrentElection(),
                    'ip' => $request->ip(),
                    'user_agent' => $request->userAgent(),
                    'reason' => 'voter not registered for current election',
                ])
                ->log('voter login rejected: not in current election');

            return response()->json([
                'status' => 'failed',
                'message' => 'No. KP ini tidak didaftarkan untuk pilihan raya semasa. Sila pergi ke kaunter IT untuk mendaftar.',
            ]);
        } catch (Exception $e) {
            activity()
                ->withProperties([
                    'no_kp' => (string) $request->input('no_kp'),
                    'ip' => $request->ip(),
                    'user_agent' => $request->userAgent(),
                    'error' => $e->getMessage(),
                ])
                ->log('voter request tac failed');

            return response()->json([
                'status' => 'failed',
                'message' => 'Error: ' . $e->getMessage(),
            ]);
        }
    }

    /**
     * One row per member per election; telefon must be the one for the current (latest) election.
     */
    private function findVoterForCurrentElection($noKp)
    {
        $electionId = Util::getCurrentElection();
        return Voter::where('no_kp', $noKp)
            ->where('election_id', $electionId)
            ->orderBy('id', 'desc')
            ->firstOrFail();
    }


    /* Check if the user wants to send OTP */
    private function wantsSendOtp(Request $request, $default = true)
    {
        // Use array_key_exists: Request::has() treats false as "missing"
        $input = $request->all();
        if (! array_key_exists('send_otp', $input)) {
            return $default;
        }

        $value = $input['send_otp'];

        if (is_bool($value)) {
            return $value;
        }

        if (is_int($value) || is_float($value)) {
            return (int) $value !== 0;
        }

        $lower = strtolower(trim((string) $value));

        if (in_array($lower, ['false', '0', 'no', 'off', ''], true)) {
            return false;
        }

        return true;
    }

    public function checkExpirationTAC($nokp): array
    {
        $currentDateTime = Carbon::now();

        $expiretac = UserOTP::where('nokp', $nokp)
            ->whereDate('created_at', '=', $currentDateTime)
            ->whereDate('expired_at', '=', $currentDateTime)
            ->whereTime('expired_at', '>=', $currentDateTime->toTimeString())
            ->whereTime('created_at', '<=', $currentDateTime->toTimeString())
            ->latest()
            ->first();

        if ($expiretac) {
            $remaining = $this->getRemainingTime($expiretac->expired_at);

            return ['notexpired' => true, 'remaining' => $remaining];
        }

        return ['notexpired' => false];
    }

    public function verifyTAC(Request $request)
    {
        $authenticateOTP = $this->getTACModel($request->no_kp, $request->token);

        if (! $authenticateOTP['condition']) {
            activity()
                ->withProperties([
                    'no_kp' => (string) $request->input('no_kp'),
                    'ip' => $request->ip(),
                    'user_agent' => $request->userAgent(),
                    'reason' => $authenticateOTP['message'] ?? 'invalid',
                ])
                ->log('voter verify tac failed');

            return response()->json([
                'status' => 'failed',
                'message' => $authenticateOTP['message'],
            ]);
        }

        if (Auth::guard('voter')->attempt(['no_kp' => $request->no_kp, 'password' => 'admin', 'election_id' => Util::getCurrentElection()])) {
            $user = Auth::guard('voter')->user();
            activity()
                ->performedOn($user)
                ->withProperties([
                    'election_id' => Util::getCurrentElection(),
                    'voter_id' => $user->id,
                    'voter_name' => $user->name,
                    'no_kp' => $user->no_kp,
                    'ip' => $request->ip(),
                    'user_agent' => $request->userAgent(),
                ])
                ->log("voter login: {$user->name}");

            return response()->json([
                'status' => 'success',
                'message' => 'Login successfully.',
                'user' => Auth::guard('voter')->user(),
                'election_status' => Util::getElectionStatus(),
                'token' => Auth::guard('voter')->user()->createToken('My Token', ['vote'])->accessToken,
            ]);
        }

        activity()
            ->withProperties([
                'no_kp' => (string) $request->input('no_kp'),
                'ip' => $request->ip(),
                'user_agent' => $request->userAgent(),
            ])
            ->log('voter login failed');

        return response()->json([
            'status' => 'failed',
            'message' => 'No. KP tidak wujud. Atau sesi mengundi telah tamat',
        ]);
    }

    private function getTACModel($no_kp, $token)
    {
        $currentDateTime = Carbon::now();

        $result = UserOTP::where('nokp', $no_kp)
            ->whereDate('created_at', '=', $currentDateTime)
            ->whereDate('expired_at', '=', $currentDateTime)
            ->whereTime('expired_at', '>=', $currentDateTime->toTimeString())
            ->whereTime('created_at', '<=', $currentDateTime->toTimeString())
            ->latest()
            ->first();

        if (! $result) {
            return ['condition' => false, 'message' => 'Token Expired'];
        }

        if ($result['token'] == $token) {
            return ['condition' => true];
        }

        return ['condition' => false, 'message' => 'Invalid Token'];
    }

    private function getTokenSMS($tele, $date)
    {
        $tac_token = $this->randum_number(6, 2, false);
        if (env('APP_ENV') == 'production') {
            $mobileno = '6' . $tele;
            $messages = sprintf(config('onewaysms.message'), $tac_token, $date);

            $parameter = [
                'apiusername' => env('ONEWAY_SMS_USERNAME'),
                'apipassword' => env('ONEWAY_SMS_PASSWORD'),
                'senderid' => env('ONEWAY_SMS_SENDERID', 'INFO'),
                'mobileno' => $mobileno,
                'message' => $messages,
                'languagetype' => env('ONEWAY_SMS_LANG', 1),
            ];

            $client = new Client(['verify' => false]);
            $status = $client->get('http://gateway.onewaysms.com.my:10001/api.aspx', [
                'query' => $parameter,
            ]);

            if ($status) {
                return $tac_token;
            }

            return 'error';
        }

        return $tac_token;
    }

    private function randum_number($len = 6, $dup = 1, $sort = false)
    {
        if ($dup < 1) {
            throw new \InvalidArgumentException('Second argument is < 1');
        }

        $num = range(0, 9);
        shuffle($num);

        $num = array_slice($num, 0, ($len - $dup) + 1);

        if ($dup > 0) {
            $k = array_rand($num, 1);
            for ($i = 0; $i < ($dup - 1); $i++) {
                $num[] = $num[$k];
            }
        }

        if ($sort) {
            sort($num);
        }

        return implode('', $num);
    }

    private function getRemainingTime($expired_at)
    {
        $currentDateTime = Carbon::now();
        $expiredAt = Carbon::parse($expired_at);

        $remainingSeconds = $currentDateTime->diffInSeconds($expiredAt);

        return gmdate('H:i:s', $remainingSeconds);
    }
}
