<?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 GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\Mail;
use Laravel\Sanctum\PersonalAccessToken;

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()]);

            $headers = [
                    'Authorization' => 'Bearer ' . $request->token,
                    'Accept'        => 'application/json',
            ];

            try {
                $response = $client->request('GET', 'oidc/userinfo.php', [
                        'headers' => $headers
                ]);
            } catch (ClientException $e) {
                $html = 'Login faild with token:' . $request->token;
                Mail::send([], [], function ($message) use ($html) {
                    $message
                      ->to('jasper@3110.nl')
                      ->subject('Login failed via SSO')
                      ->setBody($html, 'text/html');
                  });
            }

            $data = $response->getBody()->getContents();
            $data = json_decode($data);

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

            $profile_arr = explode(',', $data->profile);
            $organization_id = str_replace('KG_', '', $profile_arr[0]);

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

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

            if (!$user) {
                $new_user = [
                    'first_name' => $data->given_name,
                    'last_name' => $data->family_name,
                    'email' => $data->sub,
                    '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);
    }

    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()));
	}

	private static function dumpLocal(...$values): void
	{
		if (static::isLocal()) {
			dd(...$values);
		}
	}

	private static function isLocal(): bool
	{
		return $_SERVER['REMOTE_ADDR'] === '89.98.81.170';
	}

    public function sso()
    {
        $cookieFileHandle = tmpfile();
		$cookieFilePath = stream_get_meta_data($cookieFileHandle)['uri'];

        $ch = curl_init(static::buildSsoEndpointUri());
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_COOKIESESSION, true);
        curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFilePath);
        curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFilePath);
		$result = curl_exec($ch);
        curl_close($ch);

        fclose($cookieFileHandle);

		if (static::isLocal()) {
			echo $result; exit;
		}
        echo '<div style="display: none;">' . $result . '</div>';
    }

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

        return sprintf('%s?%s', static::determineIdpUri('oidc/authorize.php'), $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);
    }
}
