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

import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'package:uuid/uuid.dart';

import '../../globals.dart';
import '../../services/status_service.dart';
import '../../utils/network_utils.dart';
import '../exec_error.dart';
import 'credentials_intf.dart';

class OAuthCredentials implements ICredentials {
  static final _logger = Logger('$OAuthCredentials');
  String clientId;
  String clientSecret;
  final String redirectUrl;
  final String redirectScheme;
  String accessToken;

  @override
  final String serverName;

  @override
  final String id;

  OAuthCredentials({
    required this.clientId,
    required this.clientSecret,
    required this.redirectUrl,
    required this.redirectScheme,
    required this.accessToken,
    required this.serverName,
    required this.id,
  });

  factory OAuthCredentials.bootstrap(String serverName) {
    final redirectScheme = Platform.isWindows || Platform.isLinux
        ? 'http://localhost:43824'
        : 'relatica';
    final redirectUrl = Platform.isWindows || Platform.isLinux
        ? redirectScheme
        : '$redirectScheme://authorize';
    return OAuthCredentials(
      clientId: '',
      clientSecret: '',
      redirectUrl: redirectUrl,
      redirectScheme: redirectScheme,
      accessToken: '',
      serverName: serverName,
      id: const Uuid().v4(),
    );
  }

  @override
  String get authHeaderValue => 'Bearer $accessToken';

  @override
  FutureResult<ICredentials, ExecError> signIn() async {
    final result = await _getIds()
        .andThenAsync((_) async => await _login())
        .andThenSuccessAsync((_) async => this);
    return result.execErrorCast();
  }

  @override
  Map<String, dynamic> toJson() => {
        'clientId': clientId,
        'clientSecret': clientSecret,
        'redirectUrl': redirectUrl,
        'redirectScheme': redirectScheme,
        'accessToken': accessToken,
        'serverName': serverName,
        'id': id,
      };

  static OAuthCredentials fromJson(Map<String, dynamic> json) =>
      OAuthCredentials(
        clientId: json['clientId'],
        clientSecret: json['clientSecret'],
        redirectUrl: json['redirectUrl'],
        redirectScheme: json['redirectScheme'],
        accessToken: json['accessToken'],
        serverName: json['serverName'],
        id: json['id'],
      );

  FutureResult<bool, ExecError> _getIds() async {
    if (clientId.isNotEmpty || clientSecret.isNotEmpty) {
      _logger.info(
          'Client ID and Client Secret are already set, skipping ID fetching');
      return Result.ok(true);
    }
    getIt<StatusService>().setStatus('Attempting to contact $serverName...');
    final idEndpoint = Uri.parse('https://$serverName/api/v1/apps');
    final response = await postUrl(
      idEndpoint,
      {
        'client_name': 'Relatica',
        'redirect_uris': redirectUrl,
        'scopes': 'read write follow push',
        'website': 'https://myportal.social',
      },
      timeout: oauthTimeout,
    );

    response.match(onSuccess: (body) {
      getIt<StatusService>().setStatus('Connected to $serverName...');
      final json = jsonDecode(body);
      clientId = json['client_id'];
      clientSecret = json['client_secret'];
    }, onError: (error) {
      getIt<StatusService>()
          .setStatus('Error contacting $serverName...: $error');
      _logger.severe('Error contacting $serverName...: $error');
    });

    return response.mapValue((_) => true);
  }

  FutureResult<bool, ExecError> _login() async {
    if (accessToken.isNotEmpty) {
      getIt<StatusService>().setStatus('Logged into $serverName');
      _logger.info('Already have access token, skipping');
      return Result.ok(true);
    }
    // Construct the url
    final url = Uri.https(serverName, '/oauth/authorize', {
      'response_type': 'code',
      'client_id': clientId,
      'redirect_uri': redirectUrl,
      'scope': 'read write follow push',
    });

    try {
      getIt<StatusService>()
          .setStatus('Attempting getting authorization to $serverName');

      final result = await FlutterWebAuth2.authenticate(
        url: url.toString(),
        callbackUrlScheme: redirectScheme,
      );
      final code = Uri.parse(result).queryParameters['code'];
      if (code == null) {
        _logger.severe(
            'Error code was not returned with the query parameters: $result');
        getIt<StatusService>().setStatus(
            'Error getting the response code during authentication to $serverName');

        return buildErrorResult(
          type: ErrorType.serverError,
          message: 'Error getting the response code during authentication',
        );
      }
      final url2 = Uri.parse('https://$serverName/oauth/token');
      final body = {
        'client_id': clientId,
        'client_secret': clientSecret,
        'redirect_uri': redirectUrl,
        'grant_type': 'authorization_code',
        'code': code,
      };
      final response = await postUrl(url2, body);
      response.match(onSuccess: (body) {
        getIt<StatusService>().setStatus('Logged into $serverName');
        accessToken = jsonDecode(body)['access_token'];
      }, onError: (error) {
        getIt<StatusService>()
            .setStatus('Error getting authorization to $serverName');
        _logger.severe('Error doing OAUth processing: $error');
      });
    } catch (e) {
      getIt<StatusService>()
          .setStatus('Error getting authorization to $serverName');
      _logger.severe('Exception while Doing OAuth Process: $e');
      return buildErrorResult(
        type: ErrorType.serverError,
        message: 'Exception while Doing OAuth Process: $e',
      );
    }

    return Result.ok(true);
  }
}
