// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'dart:async';
import 'dart:io';

import 'package:wyatt_http_client/wyatt_http_client.dart';

String lastToken = '';
int token = 0;

void printAuth(HttpRequest req) {
  print(
    'Authorization => '
    "${req.headers.value('Authorization') ?? 'no authorization header'}",
  );
}

Future<void> handleBasic(HttpRequest req) async {
  printAuth(req);
}

Future<void> handleBasicNegotiate(HttpRequest req) async {
  if (req.headers.value('Authorization') == null) {
    req.response.statusCode = HttpStatus.unauthorized.statusCode;
    req.response.headers.set(HeaderKeys.wwwAuthenticate, 'Basic realm="Wyatt"');
    print(req.response.headers.value('WWW-Authenticate'));
    return req.response.close();
  }
  printAuth(req);
}

Future<void> handleBearer(HttpRequest req) async {
  printAuth(req);
}

Future<void> handleDigest(HttpRequest req) async {
  if (req.headers.value('Authorization') == null) {
    req.response.statusCode = HttpStatus.unauthorized.statusCode;
    req.response.headers.set(
      'WWW-Authenticate',
      'Digest realm="Wyatt", '
          'qop="auth,auth-int", '
          'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", '
          'opaque="5ccc069c403ebaf9f0171e9517f40e41"',
    );
    print(req.response.headers.value('WWW-Authenticate'));
    return req.response.close();
  }
  printAuth(req);
}

Future<void> handleUnsafe(HttpRequest req) async {
  print(
    'Query parameters => '
    '${req.uri.queryParameters.toString()}',
  );
}

Future<void> handleOauth2RefreshToken(HttpRequest req) async {
  final action = req.uri.queryParameters['action'];
  if (action == null) {
    printAuth(req);
  }

  switch (action) {
    case 'login':
      if (req.method == 'POST') {
        token++;
        req.response.write(
          '{"accessToken": "access-token-awesome$token", '
          '"refreshToken": "refresh-token-awesome$token"}',
        );
      }
      break;
    case 'refresh':
      printAuth(req);
      if (req.method == 'GET') {
        token++;
        req.response.write('{"accessToken": "access-token-refreshed$token"}');
      }
      break;
    case 'access-denied':
      final String receivedToken = req.headers.value('Authorization') ?? '';
      if (receivedToken != '' &&
          lastToken != '' &&
          receivedToken != lastToken) {
        lastToken = receivedToken;
        printAuth(req);
        return req.response.close();
      } else {
        lastToken = receivedToken;
        req.response.statusCode = HttpStatus.unauthorized.statusCode;
        return req.response.close();
      }
    default:
      break;
  }
}

Future<void> server() async {
  final server = await HttpServer.bind(InternetAddress.anyIPv6, 8080);
  var error = 0;
  var token = 0;
  await server.forEach((request) {
    print('[${request.method}] ${request.uri}');
    switch (request.uri.path) {
      case '/test/basic-test':
        handleBasic(request);
        break;
      case '/test/basic-test-with-negotiate':
        handleBasicNegotiate(request);
        break;
      case '/test/digest-test':
        handleDigest(request);
        break;
      case '/test/apikey-test':
        handleBearer(request);
        break;
      case '/test/bearer-test':
        handleBearer(request);
        break;
      case '/test/unsafe-test':
        handleUnsafe(request);
        break;
      case '/test/oauth2-test':
        handleOauth2RefreshToken(request);
        break;

      case '/test/bearer-login':
        if (request.method == 'POST') {
          request.response.write('{"token": "access-token-test"}');
        }
        break;

      case '/test/oauth2-test-error':
        error++;
        print('Error $error');
        if (error >= 3) {
          print('Authorized');
          error = 0;
        } else {
          request.response.statusCode = HttpStatus.unauthorized.statusCode;
        }
        break;
      case '/test/oauth2-test-timeout':
        error++;
        print('Error $error');
        request.response.statusCode = HttpStatus.unauthorized.statusCode;
        break;
      case '/test/oauth2-login':
        if (request.method == 'POST') {
          token++;
          request.response.write(
            '{"accessToken": "access-token-awesome$token", '
            '"refreshToken": "refresh-token-awesome$token"}',
          );
        }
        break;
      case '/test/oauth2-refresh':
        print(
          'Authorization => '
          "${request.headers.value('Authorization') ?? 'no refresh token'}",
        );
        if (request.method == 'GET') {
          token++;
          request.response
              .write('{"accessToken": "access-token-refreshed$token"}');
        }
        break;
      case '/test/oauth2-refresh-error':
        request.response.statusCode = HttpStatus.unauthorized.statusCode;
        break;

      default:
        print(' => Unknown path or method');
        request.response.statusCode = HttpStatus.notFound.statusCode;
    }
    request.response.close();
    print('====================');
  });
}

Future<void> main() async {
  unawaited(server());
  const base = 'localhost:8080';
  final uriPrefix = UriPrefixMiddleware(
    protocol: Protocols.http,
    authority: base,
  );
  final jsonEncoder = BodyToJsonMiddleware();
  final logger = SimpleLoggerMiddleware();

  // Basic
  final basicAuth = BasicAuthMiddleware(
    username: 'username',
    password: 'password',
  );
  final basic = MiddlewareClient(
    pipeline: Pipeline.fromIterable([
      uriPrefix,
      basicAuth,
      logger,
    ]),
  );
  await basic.get(Uri.parse('/test/basic-test'));

  // Digest
  final digestAuth = DigestAuthMiddleware(
    username: 'Mufasa',
    password: 'Circle Of Life',
  );
  final digest = MiddlewareClient(
    pipeline: Pipeline.fromIterable([
      uriPrefix,
      digestAuth,
      logger,
    ]),
  );
  await digest.get(Uri.parse('/test/digest-test'));

  // // Bearer
  // final bearer = BearerAuthenticationClient(
  //   token: 'access-token-test',
  //   inner: restClient,
  // );
  // await bearer.get(Uri.parse('/test/bearer-test'));

  // // API Key
  // final apiKey = BearerAuthenticationClient(
  //   token: 'awesome-api-key',
  //   authenticationMethod: 'ApiKey',
  //   inner: restClient,
  // );
  // await apiKey.get(Uri.parse('/test/apikey-test'));

  // Unsafe URL
  final unsafeAuth = UnsafeAuthMiddleware(
    username: 'Mufasa',
    password: 'Circle Of Life',
  );
  final unsafe = MiddlewareClient(
    pipeline: Pipeline.fromIterable([
      uriPrefix,
      unsafeAuth,
      logger,
    ]),
  );
  await unsafe.get(Uri.parse('/test/unsafe-test'));

  // OAuth2
  final refreshTokenAuth = RefreshTokenAuthMiddleware(
    authorizationEndpoint: '/test/oauth2-test?action=login',
    tokenEndpoint: '/test/oauth2-test?action=refresh',
    accessTokenParser: (body) => body['accessToken']! as String,
    refreshTokenParser: (body) => body['refreshToken']! as String,
  );
  final refreshToken = MiddlewareClient(
    pipeline: Pipeline.fromIterable([
      uriPrefix,
      jsonEncoder,
      refreshTokenAuth,
      logger,
    ]),
  );
  await refreshToken.get(Uri.parse('/test/oauth2-test'));
  // Login
  await refreshToken.post(
    Uri.parse('/test/oauth2-test'),
    body: <String, String>{
      'username': 'username',
      'password': 'password',
    },
  );
  await refreshToken.get(Uri.parse('/test/oauth2-test'));
  // await refreshToken.refresh();
  // await refreshToken.get(Uri.parse('/test/oauth2-test'));
  // await refreshToken.get(Uri.parse('/test/oauth2-test?action=access-denied'));

  exit(0);
}
