<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Response\ApiResponse;
use Illuminate\Http\Request;
use App\Services\UserService;
use App\Services\MemberService;
use Illuminate\Support\Carbon;
use App\Http\Requests\User\Login;
use Illuminate\Support\Facades\Hash;
use App\Http\Resources\UserLoggedResource;
use App\Repositories\Role;
use Redirect;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\Mail;
use Laravel\Sanctum\PersonalAccessToken;
use Illuminate\Support\Facades\Log;
class AuthController extends Controller
{
    private $userService;
    private $memberService;

    public function __construct(
        UserService $userService,
        MemberService $memberService
    ) {

        $this->userService = $userService;
        $this->memberService = $memberService;

        $this->middleware('auth:sanctum', [
            'except' => [
                'get_sso_data',
                'login',
                'signup',
                'sso',
                'status',
            ],
        ]);
    }

    public function login(Login $request)
    {
        if (empty($request->token)) {
            $user = $this->userService->getByEmailWith($request->email, ['roles']);

            if (!$user || !Hash::check($request->password, $user->password)) {
                return response(['error' => ['The provided credentials are incorrect.']]);
            }

        } else {
            $client = new \GuzzleHttp\Client(['base_uri' => static::determineIdpUri()]);
            
            try {
                $response = $client->request('POST', 'protocol/openid-connect/token', [
                    'form_params' => [
                        'code' => $request->token,
                        'client_id' => config('sso.client_id'),
                        'client_secret' => config('sso.secret'),
                        'redirect_uri' => config('sso.redirect_uri'),
                        'scope' => 'openid profile',
                        'grant_type' => 'authorization_code'
                    ]
                ]);

                $token = json_decode($response->getBody(), true)['id_token'];

                $decodedToken = \Firebase\JWT\JWT::decode($token, $this->getJwks(), false);
               
            } catch (ClientException $e) {
                $html = 'Login faild with token:' . $request->token;
                Mail::send([], [], function ($message) use ($html) {
                    $message
                      ->to('joris@ggzecademy.nl')
                      ->subject('Login failed via SSO')
                      ->setBody($html, 'text/html');
                  });
            }

            
            if (empty($decodedToken->kg) || !stristr($decodedToken->kg, 'KG')) {
                return response(['error' => ['The provided credentials are incorrect.']]);
            }

            $organization_id = str_replace('KG_', '', $decodedToken->kg);

            $member = $this->memberService->get($organization_id);

            $user = $this->userService->getByEmailWith($decodedToken->email, ['roles']);

            if (!$user) {
                $new_user = [
                    'first_name' => $decodedToken->given_name,
                    'last_name' => $decodedToken->family_name,
                    'email' => $decodedToken->email,
                    'password' => 'aT@5*Wb*W7gseVhC',
                ];

                $user = $this->userService->save($new_user, false);
                $user->roles()->attach(Role::whereName('user')->firstOrFail());
            }

            $member->users()->syncWithoutDetaching([$user->id]);
            $member->save();
        }

        $user->last_login_at = $user->logged_at;
        $user->last_login_ip = $request->getClientIp();
        $user->logged_at = Carbon::now();
        $user->save();

        $token = $user->createToken('cms-token')->plainTextToken;

        $response = ['token' => $token];

        return response($response, 201);
    }

    private function getJwks(): array
    {
        $jwkData = json_decode(
            file_get_contents(static::determineIdpUri() . 'protocol/openid-connect/certs'),
            true
        );

        return \Firebase\JWT\JWK::parseKeySet($jwkData);
    }

    public function logout(Request $request)
    {
        $request->user()->tokens()->delete();

        return response()->json('Logged-out', 201);
    }

    public function status(Request $request)
    {
        $bearerToken = $request->bearerToken();

        if (is_null($bearerToken)) {
            return ApiResponse::error(
                new Error\AuthController\MissingBearerTokenError(),
            );
        } else {
            $accessToken = PersonalAccessToken::findToken($bearerToken);

            return ApiResponse::success([
                'authenticated' => $accessToken instanceof PersonalAccessToken,
                'user' => $accessToken ? $accessToken->tokenable->email : null,
            ]);
        }
    }

    public function me(Request $request)
    {
        return response()->json(new UserLoggedResource($request->user()));
    }

    public function sso()
    {
        return Redirect::to(static::buildSsoEndpointUri());
    }

    private static function buildSsoEndpointUri(): string
    {
        $endpointQuery = http_build_query([
            'client_id' => config('sso.client_id'),
            'redirect_uri' => config('sso.redirect_uri'),
            'scope' => 'openid profile',
            'response_type' => 'code',
            'nonce' => md5(rand()), // TODO: proper nonce handling
        ], '', '&', PHP_QUERY_RFC3986);
        $endpointQuery = str_replace('%2B', '+', $endpointQuery);

        return sprintf('%s?%s', static::determineIdpUri('protocol/openid-connect/auth'), $endpointQuery);
    }

    private static function determineIdpUri(string $path = ''): string
    {
        $baseUri = rtrim(config('sso.idp_base_uri'), '/');

        return sprintf('%s/%s', $baseUri, $path);
    }

    public function get_sso_data() {
        $client = new \GuzzleHttp\Client([
            'base_uri' => static::determineIdpUri(),
            'headers' => [
                'Authorization' => sprintf('Bearer %s', $_GET['token']),
                'Accept' => 'application/json',
            ],
        ]);

        $response = $client->request('GET', 'oidc/userinfo.php');

        return response($response->getBody()->getContents(), 201);
    }
}