content: // Copyright 2020-present the Saltech Systems authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  // http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  import 'dart:async';
  import 'dart:convert';
  import 'dart:io';
  
  import 'package:http/http.dart';
  import 'package:http/src/utils.dart';
  import 'package:pedantic/pedantic.dart';
  import 'package:flutter_test/flutter_test.dart';
  import 'package:matcher/src/type_matcher.dart';
  
  export 'http_utils.dart';
  
  /// The current server instance.
  HttpServer? _server;
  
  /// The URL for the current server instance.
  Uri get serverUrl => Uri.parse('http://localhost:${_server!.port}');
  
  /// Starts a new HTTP server.
  Future<void> startServer() async {
    _server = (await HttpServer.bind('localhost', 0))
      ..listen((request) async {
        var path = request.uri.path;
        var response = request.response;
  
        if (path == '/oauth/v2/token') {
          response
            ..statusCode = 200
            ..contentLength = 0;
          unawaited(response.close());
          return;
        }
  
        if (path == '/error') {
          response
            ..statusCode = 400
            ..contentLength = 0;
          unawaited(response.close());
          return;
        }
  
        if (path == '/loop') {
          var n = int.parse(request.uri.query);
          response
            ..statusCode = 302
            ..headers
                .set('location', serverUrl.resolve('/loop?${n + 1}').toString())
            ..contentLength = 0;
          unawaited(response.close());
          return;
        }
  
        if (path == '/redirect') {
          response
            ..statusCode = 302
            ..headers.set('location', serverUrl.resolve('/').toString())
            ..contentLength = 0;
          unawaited(response.close());
          return;
        }
  
        if (path == '/no-content-length') {
          response
            ..statusCode = 200
            ..contentLength = -1
            ..write('body');
          unawaited(response.close());
          return;
        }
  
        var requestBodyBytes = await ByteStream(request).toBytes();
        var encodingName = request.uri.queryParameters['response-encoding'];
        var outputEncoding = encodingName == null
            ? ascii
            : requiredEncodingForCharset(encodingName);
  
        response.headers.contentType =
            ContentType('application', 'json', charset: outputEncoding.name);
        response.headers.set('single', 'value');
  
        dynamic requestBody;
        if (requestBodyBytes.isEmpty) {
          requestBody = null;
        } else if (request.headers.contentType?.charset != null) {
          var encoding =
              requiredEncodingForCharset(request.headers.contentType!.charset!);
          requestBody = encoding.decode(requestBodyBytes);
        } else {
          requestBody = requestBodyBytes;
        }
  
        var content = <String, dynamic>{
          'method': request.method,
          'path': request.uri.path,
          'headers': {}
        };
        if (requestBody != null) content['body'] = requestBody;
        request.headers.forEach((name, values) {
          // These headers are automatically generated by dart:io, so we don't
          // want to test them here.
          if (name == 'cookie' || name == 'host') return;
  
          content['headers'][name] = values;
        });
  
        var body = json.encode(content);
        response
          ..contentLength = body.length
          ..write(body);
        unawaited(response.close());
      });
  }
  
  /// Stops the current HTTP server.
  void stopServer() {
    if (_server != null) {
      _server!.close();
      _server = null;
    }
  }
  
  /// A matcher for functions that throw HttpException.
  Matcher get throwsClientException => throwsA(TypeMatcher<ClientException>());
  
  /// A matcher for functions that throw SocketException.
  final Matcher throwsSocketException =
      throwsA(const TypeMatcher<SocketException>());
  