import 'dart:convert';

import 'package:html/parser.dart';
import 'package:http/http.dart' as http;
import 'package:result_monad/result_monad.dart';

import '../models/exec_error.dart';
import '../models/link_preview_data.dart';

const ogTitleKey = 'og:title';
const ogDescriptionKey = 'og:description';
const ogSiteNameKey = 'og:site_name';
const ogImageKey = 'og:image';

FutureResult<LinkPreviewData, ExecError> getLinkPreview(String url) async {
  final result = await _getOpenGraphData(url);
  return result.andThenSuccess((ogData) {
    final title = ogData.getValue(ogTitleKey);
    final description = ogData.getValue(ogDescriptionKey);
    final siteName = ogData.getValue(ogSiteNameKey);
    final availableImageUrls = ogData.getValues(ogImageKey);
    final selectedImageUrl =
        availableImageUrls.isEmpty ? '' : availableImageUrls.first;
    return LinkPreviewData(
      link: url,
      title: title,
      description: description,
      siteName: siteName,
      availableImageUrls: availableImageUrls,
      selectedImageUrl: selectedImageUrl,
    );
  }).execErrorCast();
}

FutureResult<List<MapEntry<String, String>>, dynamic> _getOpenGraphData(
    String url) async {
  return runCatchingAsync<List<MapEntry<String, String>>>(() async {
    final response = await http.get(Uri.parse(url));
    if (response.statusCode != 200) {
      return buildErrorResult(
        type: ErrorType.serverError,
        message: 'Error getting link preview: ${response.statusCode}',
      );
    }

    final rawHtml = utf8.decode(response.bodyBytes);
    final htmlDoc = parse(rawHtml);
    final openGraphTags = htmlDoc.head
        ?.querySelectorAll("[property*='og:']")
        .map((p) {
          final key = p.attributes['property'] ?? '';
          final value = p.attributes['content'] ?? '';
          return MapEntry(key, value);
        })
        .where((e) => e.key.isNotEmpty)
        .toList();
    return Result.ok(openGraphTags ?? []);
  });
}

extension OpenGraphFinders on List<MapEntry<String, String>> {
  String getValue(String key) {
    return firstWhere(
      (e) => e.key == key,
      orElse: () => const MapEntry('', ''),
    ).value;
  }

  List<String> getValues(String key) {
    return where((e) => e.key == key).map((e) => e.value).toList();
  }
}
