import 'dart:convert';

import 'package:result_monad/result_monad.dart';

import '../models/exec_error.dart';
import '../models/server_data.dart';
import '../utils/network_utils.dart';

class FediverseServiceValidator {
  static const softwareTypeDiaspora = 'diaspora';

  final knownServers = <String, ServerData>{};

  FutureResult<ServerData, ExecError> getServerData(String url) async {
    final uri = Uri.tryParse(url);
    if (uri == null || uri.scheme != 'https') {
      return buildErrorResult(
        type: ErrorType.parsingError,
        message: 'Invalid URL: $url',
      );
    }

    final domain = uri.host;
    ServerData? data = knownServers[domain];
    if (data != null) {
      return Result.ok(data);
    }

    final serverData = await refreshServerData(domain);
    knownServers[domain] = serverData;
    return Result.ok(serverData);
  }

  FutureResult<bool, ExecError> checkIfFediverseLink(String url) async {
    final dataResult = await getServerData(url);
    return dataResult.mapValue((d) => d.isFediverse);
  }

  static Future<ServerData> refreshServerData(String domainName) async {
    final uri = Uri.https(
      domainName,
      '/.well-known/nodeinfo',
    );
    final result = await getUrl(uri)
        .andThenSuccessAsync((page) async {
          return jsonDecode(page.data);
        })
        .andThenAsync(
          (json) async =>
              json is Map<String, dynamic> ? Result.ok(json) : Result.error(''),
        )
        .andThenSuccessAsync((json) async => json['links'] ?? [])
        .andThenAsync(
          (nodeInfos) async => nodeInfos.isNotEmpty
              ? Result.ok(nodeInfos.last)
              : Result.error(''),
        )
        .andThenAsync((nodeInfo) async {
          final rel = nodeInfo['rel']?.toString() ?? '';
          if (!rel.startsWith('http://nodeinfo.diaspora.software/ns/schema/')) {
            return Result.error('');
          }

          final nodeInfoUrl = Uri.tryParse(nodeInfo['href'] ?? '');
          if (nodeInfoUrl == null) {
            return Result.error('');
          }
          return await getUrl(nodeInfoUrl);
        })
        .andThenSuccessAsync(
            (nodeInfoData) async => jsonDecode(nodeInfoData.data))
        .andThenSuccessAsync((nodeInfoJson) async {
          final softwareName =
              nodeInfoJson['software']?['name']?.toString() ?? '';
          final softwareVersion =
              nodeInfoJson['software']?['version']?.toString() ?? '';
          final isFediverse =
              softwareName.isNotEmpty && softwareVersion.isNotEmpty;
          return ServerData(
            domainName: domainName,
            isFediverse: isFediverse,
            softwareName: softwareName,
            softwareVersion: softwareVersion,
          );
        });

    return result.getValueOrElse(
        () => ServerData(domainName: domainName, isFediverse: false));
  }
}
