import 'dart:convert';
import 'dart:io';

import 'package:platform_container/mirrors.dart';
import 'package:platform_foundation/core.dart';
import 'package:platform_foundation/http.dart';
import 'package:http/http.dart' as http;
import 'package:io/ansi.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';

import 'common.dart';

@Middleware([interceptor])
Future<String> testMiddlewareMetadata(
    RequestContext req, ResponseContext res) async {
  return 'This should not be shown.';
}

@Middleware([interceptService])
class QueryService extends Service {
  @override
  @Middleware([interceptor])
  Future<Map?> read(id, [Map? params]) async => params;
}

void interceptor(RequestContext req, ResponseContext res) {
  res
    ..write('Middleware')
    ..close();
}

bool interceptService(RequestContext req, ResponseContext res) {
  res.write('Service with ');
  return true;
}

void main() {
  late Application app;
  late Application nested;
  late Application todos;
  late String url;
  late http.Client client;

  setUp(() async {
    app = Application(reflector: MirrorsReflector());
    nested = Application(reflector: MirrorsReflector());
    todos = Application(reflector: MirrorsReflector());

    for (var app in [app, nested, todos]) {
      app.logger = Logger('routing_test')
        ..onRecord.listen((rec) {
          if (rec.error != null) {
            stdout
              ..writeln(cyan.wrap(rec.toString()))
              ..writeln(cyan.wrap(rec.error.toString()))
              ..writeln(cyan.wrap(rec.stackTrace.toString()));
          }
        });
    }

    todos.get('/action/:action', (req, res) => res.json(req.params));

    late Route ted;

    ted = nested.post('/ted/:route', (RequestContext req, res) {
      print('Params: ${req.params}');
      print('Path: ${ted.path}, uri: ${req.path}');
      print('matcher: ${ted.parser}');
      return req.params;
    });

    app.mount('/nes', nested);
    app.get('/meta', testMiddlewareMetadata);
    app.get('/intercepted', (req, res) => 'This should not be shown',
        middleware: [interceptor]);
    app.get('/hello', (req, res) => 'world');
    app.get('/name/:first/last/:last', (req, res) => req.params);
    app.post(
        '/lambda',
        (RequestContext req, res) =>
            req.parseBody().then((_) => req.bodyAsMap));
    app.mount('/todos/:id', todos);
    app
        .get('/greet/:name',
            (RequestContext req, res) async => "Hello ${req.params['name']}")
        .name = 'Named routes';
    app.get('/named', (req, ResponseContext res) async {
      await res.redirectTo('Named routes', {'name': 'tests'});
    });
    app.get('/log', (RequestContext req, res) async {
      print('Query: ${req.queryParameters}');
      return 'Logged';
    });

    app.get('/method', (req, res) => 'Only GET');
    app.post('/method', (req, res) => 'Only POST');

    app.use('/query', QueryService());

    RequestHandler write(String message) {
      return (req, res) {
        res.write(message);
        return true;
      };
    }

    app.chain([write('a')]).chain([write('b'), write('c')]).get(
        '/chained', (req, res) => res.close());

    app.fallback((req, res) => 'MJ');

    //app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);

    client = http.Client();
    var server = await PlatformHttp(app).startServer('127.0.0.1', 0);
    url = 'http://${server.address.host}:${server.port}';
  });

  tearDown(() async {
    await app.close();
    client.close();
  });

  test('Can match basic url', () async {
    var response = await client.get(Uri.parse('$url/hello'));
    expect(response.body, equals('"world"'));
  });

  test('Can match url with multiple parameters', () async {
    var response = await client.get(Uri.parse('$url/name/HELLO/last/WORLD'));
    print('Response: ${response.body}');
    var json_ = json.decode(response.body);
    expect(json_, const IsInstanceOf<Map>());
    expect(json_['first'], equals('HELLO'));
    expect(json_['last'], equals('WORLD'));
  });

  test('Chained routes', () async {
    var response = await client.get(Uri.parse('$url/chained'));
    expect(response.body, equals('abc'));
  });

  test('Can nest another Protevus instance', () async {
    var response = await client.post(Uri.parse('$url/nes/ted/foo'));
    var json_ = json.decode(response.body);
    expect(json_['route'], equals('foo'));
  });

  test('Can parse parameters from a nested Protevus instance', () async {
    var response = await client.get(Uri.parse('$url/todos/1337/action/test'));
    var json_ = json.decode(response.body);
    print('JSON: $json_');
    expect(json_['id'], equals('1337'));
    expect(json_['action'], equals('test'));
  });

  test('Can add and use named middleware', () async {
    var response = await client.get(Uri.parse('$url/intercepted'));
    expect(response.body, equals('Middleware'));
  });

  test('Middleware via metadata', () async {
    // Metadata
    var response = await client.get(Uri.parse('$url/meta'));
    expect(response.body, equals('Middleware'));
  });

  test('Can serialize function result as JSON', () async {
    Map headers = <String, String>{'Content-Type': 'application/json'};
    var postData = json.encode({'it': 'works'});
    var response = await client.post(Uri.parse('$url/lambda'),
        headers: headers as Map<String, String>, body: postData);
    print('Response: ${response.body}');
    expect(json.decode(response.body)['it'], equals('works'));
  });

  test('Fallback routes', () async {
    var response = await client.get(Uri.parse('$url/my_favorite_artist'));
    expect(response.body, equals('"MJ"'));
  });

  /* TODO: Revisit this later
  test('Can name routes', () {
    Route foo = app.get('/framework/:id', null)..name = 'frm';
    print('Foo: $foo');
    String uri = foo.makeUri({'id': 'Protevus'});
    print(uri);
    expect(uri, equals('framework/Protevus'));
  });
  */

  test('Redirect to named routes', () async {
    var response = await client.get(Uri.parse('$url/named'));
    print(response.body);
    expect(json.decode(response.body), equals('Hello tests'));
  });

  test('Match routes, even with query params', () async {
    var response = await client
        .get(Uri.parse('$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo'));
    print(response.body);
    expect(json.decode(response.body), equals('Logged'));

    response = await client.get(Uri.parse('$url/query/foo?bar=baz'));
    print(response.body);
    expect(response.body, equals('Service with Middleware'));
  });

  test('only match route with matching method', () async {
    var response = await client.get(Uri.parse('$url/method'));
    print(response.body);
    expect(response.body, '"Only GET"');

    response = await client.post(Uri.parse('$url/method'));
    print(response.body);
    expect(response.body, '"Only POST"');

    response = await client.patch(Uri.parse('$url/method'));
    print(response.body);
    expect(response.body, '"MJ"');
  });
}
