// 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 .
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';
import 'package:wyatt_http_client/src/middleware_client.dart';
import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart';
import 'package:wyatt_http_client/src/middlewares/refresh_token_middleware.dart';
import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart';
import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart';
import 'package:wyatt_http_client/src/pipeline.dart';
import 'package:wyatt_http_client/src/utils/http_status.dart';
import 'package:wyatt_http_client/src/utils/protocols.dart';
enum EmailVerificationAction {
  signUp,
  resetPassword,
  changeEmail;
  String toSnakeCase() {
    return name.splitMapJoin(
      RegExp('[A-Z]'),
      onMatch: (m) => '_${m[0]?.toLowerCase()}',
      onNonMatch: (n) => n,
    );
  }
  factory EmailVerificationAction.fromString(String str) {
    return EmailVerificationAction.values.firstWhere(
      (EmailVerificationAction element) => element.toSnakeCase() == str,
    );
  }
}
class VerifyCode {
  final String email;
  final String verificationCode;
  final EmailVerificationAction action;
  VerifyCode({
    required this.email,
    required this.verificationCode,
    required this.action,
  });
  VerifyCode copyWith({
    String? email,
    String? verificationCode,
    EmailVerificationAction? action,
  }) {
    return VerifyCode(
      email: email ?? this.email,
      verificationCode: verificationCode ?? this.verificationCode,
      action: action ?? this.action,
    );
  }
  Map toMap() {
    return {
      'email': email,
      'verification_code': verificationCode,
      'action': action.toSnakeCase(),
    };
  }
  factory VerifyCode.fromMap(Map map) {
    return VerifyCode(
      email: map['email'] as String,
      verificationCode: map['verification_code'] as String,
      action: EmailVerificationAction.fromString(map['action'] as String),
    );
  }
  String toJson() => json.encode(toMap());
  factory VerifyCode.fromJson(String source) =>
      VerifyCode.fromMap(json.decode(source) as Map);
  @override
  String toString() =>
      'VerifyCode(email: $email, verificationCode: $verificationCode, action: $action)';
}
class Account {
  final String email;
  final String? sessionId;
  Account({
    required this.email,
    this.sessionId,
  });
  Account copyWith({
    String? email,
    String? sessionId,
  }) {
    return Account(
      email: email ?? this.email,
      sessionId: sessionId ?? this.sessionId,
    );
  }
  Map toMap() {
    return {
      'email': email,
      'session_id': sessionId,
    };
  }
  factory Account.fromMap(Map map) {
    return Account(
      email: map['email'] as String,
      sessionId: map['session_id'] != null ? map['session_id'] as String : null,
    );
  }
  String toJson() => json.encode(toMap());
  factory Account.fromJson(String source) =>
      Account.fromMap(json.decode(source) as Map);
  @override
  String toString() => 'Account(email: $email, sessionId: $sessionId)';
}
class SignUp {
  final String sessionId;
  final String password;
  SignUp({
    required this.sessionId,
    required this.password,
  });
  SignUp copyWith({
    String? sessionId,
    String? password,
  }) {
    return SignUp(
      sessionId: sessionId ?? this.sessionId,
      password: password ?? this.password,
    );
  }
  Map toMap() {
    return {
      'session_id': sessionId,
      'password': password,
    };
  }
  factory SignUp.fromMap(Map map) {
    return SignUp(
      sessionId: map['session_id'] as String,
      password: map['password'] as String,
    );
  }
  String toJson() => json.encode(toMap());
  factory SignUp.fromJson(String source) =>
      SignUp.fromMap(json.decode(source) as Map);
  @override
  String toString() => 'SignUp(sessionId: $sessionId, password: $password)';
}
class TokenSuccess {
  final String accessToken;
  final String refreshToken;
  final Account account;
  TokenSuccess({
    required this.accessToken,
    required this.refreshToken,
    required this.account,
  });
  TokenSuccess copyWith({
    String? accessToken,
    String? refreshToken,
    Account? account,
  }) {
    return TokenSuccess(
      accessToken: accessToken ?? this.accessToken,
      refreshToken: refreshToken ?? this.refreshToken,
      account: account ?? this.account,
    );
  }
  Map toMap() {
    return {
      'access_token': accessToken,
      'refresh_token': refreshToken,
      'account': account.toMap(),
    };
  }
  factory TokenSuccess.fromMap(Map map) {
    return TokenSuccess(
      accessToken: map['access_token'] as String,
      refreshToken: map['refresh_token'] as String,
      account: Account.fromMap(map['account'] as Map),
    );
  }
  String toJson() => json.encode(toMap());
  factory TokenSuccess.fromJson(String source) =>
      TokenSuccess.fromMap(json.decode(source) as Map);
  @override
  String toString() =>
      'TokenSuccess(accessToken: $accessToken, refreshToken: $refreshToken, account: $account)';
}
class Login {
  final String email;
  final String password;
  Login({
    required this.email,
    required this.password,
  });
  Login copyWith({
    String? email,
    String? password,
  }) {
    return Login(
      email: email ?? this.email,
      password: password ?? this.password,
    );
  }
  Map toMap() {
    return {
      'email': email,
      'password': password,
    };
  }
  factory Login.fromMap(Map map) {
    return Login(
      email: map['email'] as String,
      password: map['password'] as String,
    );
  }
  String toJson() => json.encode(toMap());
  factory Login.fromJson(String source) =>
      Login.fromMap(json.decode(source) as Map);
  @override
  String toString() => 'Login(email: $email, password: $password)';
}
class FastAPI {
  final String baseUrl;
  final MiddlewareClient client;
  final int apiVersion;
  FastAPI({
    this.baseUrl = 'localhost:80',
    MiddlewareClient? client,
    this.apiVersion = 1,
  }) : client = client ?? MiddlewareClient();
  String get apiPath => '/api/v$apiVersion';
  Future sendSignUpCode(String email) async {
    final r = await client.post(
      Uri.parse('$apiPath/auth/send-sign-up-code'),
      body: {
        'email': email,
      },
    );
    if (r.statusCode != 201) {
      throw Exception('Invalid reponse: ${r.statusCode}');
    }
  }
  Future verifyCode(VerifyCode verifyCode) async {
    final r = await client.post(
      Uri.parse('$apiPath/auth/verify-code'),
      body: verifyCode.toMap(),
    );
    if (r.statusCode != 202) {
      throw Exception('Invalid reponse: ${r.statusCode}');
    } else {
      return Account.fromMap(
        (jsonDecode(r.body) as Map)['account']
            as Map,
      );
    }
  }
  Future signUp(SignUp signUp) async {
    final r = await client.post(
      Uri.parse('$apiPath/auth/sign-up'),
      body: signUp.toMap(),
    );
    if (r.statusCode != 201) {
      throw Exception('Invalid reponse: ${r.statusCode}');
    } else {
      return Account.fromJson(r.body);
    }
  }
  Future signInWithPassword(Login login) async {
    final r = await client.post(
      Uri.parse('$apiPath/auth/sign-in-with-password'),
      body: login.toMap(),
    );
    if (r.statusCode != 200) {
      throw Exception('Invalid reponse: ${r.statusCode}');
    } else {
      return TokenSuccess.fromJson(r.body);
    }
  }
  // Future refresh() async {
  //   final r = await client.refresh();
  //   return TokenSuccess.fromJson(r?.body ?? '');
  // }
  Future> getAccountList() async {
    final r = await client.get(
      Uri.parse('$apiPath/account'),
    );
    if (r.statusCode != 200) {
      throw Exception('Invalid reponse: ${r.statusCode}');
    } else {
      final list = (jsonDecode(r.body) as Map)['founds']
          as List