using YMhut.Box.Core;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.Net;

namespace YMhut.Box.Core.Api;

public enum ApiHealthStatus
{
    Unknown,
    Healthy,
    Degraded,
    Unhealthy
}

public sealed record ApiResponse(
    string EndpointId,
    Uri Uri,
    bool Success,
    string Content,
    string? Error,
    DateTimeOffset FetchedAt,
    int StatusCode = 0);

public interface IApiManager
{
    Task<ApiResponse> FetchAsync(string endpointId, string input = "", CancellationToken cancellationToken = default);

    Task<ApiResponse> FetchUriAsync(string endpointId, Uri uri, string input = "", CancellationToken cancellationToken = default);

    Task<ApiHealthStatus> CheckHealthAsync(string endpointId, string input = "", CancellationToken cancellationToken = default);
}

public sealed class ApiManager(IHttpService httpService, ILogService? logService = null) : IApiManager
{
    private readonly Dictionary<string, (DateTimeOffset fetchedAt, ApiResponse response)> _cache = new(StringComparer.OrdinalIgnoreCase);
    private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);

    public async Task<ApiResponse> FetchAsync(string endpointId, string input = "", CancellationToken cancellationToken = default)
    {
        if (!ApiEndpoints.TryResolve(endpointId, input, out var endpoint, out var uri))
        {
            return new ApiResponse(endpointId, new Uri("about:blank"), false, string.Empty, "No upstream endpoint is configured for this tool.", DateTimeOffset.Now);
        }

        return await FetchUriAsync(endpoint.Id, uri, input, cancellationToken).ConfigureAwait(false);
    }

    public async Task<ApiResponse> FetchUriAsync(string endpointId, Uri uri, string input = "", CancellationToken cancellationToken = default)
    {
        var cacheKey = $"{endpointId}|{uri}|{input.Trim()}";
        if (_cache.TryGetValue(cacheKey, out var cached) && DateTimeOffset.Now - cached.fetchedAt < _cacheDuration)
        {
            return cached.response;
        }

        var endpoint = ApiEndpoints.TryGet(endpointId, out var knownEndpoint) ? knownEndpoint : null;
        var endpointName = endpoint?.Name ?? endpointId;
        try
        {
            var result = await httpService.SendAsync(
                uri,
                ensureSuccess: false,
                policy: ResolvePolicy(endpointId),
                cancellationToken: cancellationToken).ConfigureAwait(false);
            if ((int)result.StatusCode < 200 || (int)result.StatusCode >= 300)
            {
                return new ApiResponse(
                    endpointId,
                    uri,
                    false,
                    result.Content,
                    $"HTTP {(int)result.StatusCode} {result.StatusCode}",
                    DateTimeOffset.Now,
                    (int)result.StatusCode);
            }

            var response = new ApiResponse(endpointId, uri, true, result.Content, null, DateTimeOffset.Now, (int)result.StatusCode);
            _cache[cacheKey] = (DateTimeOffset.Now, response);
            return response;
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            return new ApiResponse(endpointId, uri, false, string.Empty, "Request canceled.", DateTimeOffset.Now);
        }
        catch (HttpRequestTimeoutException exception)
        {
            var error = FriendlyTimeoutMessage(exception.Timeout);
            await WriteFailureLogAsync(endpointName, endpoint, error, cancellationToken).ConfigureAwait(false);
            return new ApiResponse(endpointId, uri, false, string.Empty, error, DateTimeOffset.Now);
        }
        catch (TimeoutException)
        {
            var error = FriendlyTimeoutMessage(ResolvePolicy(endpointId).Timeout);
            await WriteFailureLogAsync(endpointName, endpoint, error, cancellationToken).ConfigureAwait(false);
            return new ApiResponse(endpointId, uri, false, string.Empty, error, DateTimeOffset.Now);
        }
        catch (HttpRequestException exception)
        {
            const string error = "Network request failed. Check the network connection or proxy settings, then retry.";
            var detail = endpoint is { ShouldHideEndpoint: true } ? "Sensitive endpoint hidden." : SensitiveText.Sanitize(exception.Message);
            await (logService?.WriteAsync("Error", "api", $"{endpointName} request failed", detail, cancellationToken) ?? Task.CompletedTask)
                .ConfigureAwait(false);
            return new ApiResponse(endpointId, uri, false, string.Empty, error, DateTimeOffset.Now);
        }
        catch (Exception exception)
        {
            var error = endpoint is { ShouldHideEndpoint: true }
                ? "Remote data is temporarily unavailable. Please retry later."
                : SensitiveText.Sanitize(exception.Message);
            await WriteFailureLogAsync(endpointName, endpoint, error, cancellationToken).ConfigureAwait(false);
            return new ApiResponse(endpointId, uri, false, string.Empty, error, DateTimeOffset.Now);
        }
    }

    public async Task<ApiHealthStatus> CheckHealthAsync(string endpointId, string input = "", CancellationToken cancellationToken = default)
    {
        var response = await FetchAsync(endpointId, input, cancellationToken).ConfigureAwait(false);
        if (!response.Success)
        {
            return ApiHealthStatus.Unhealthy;
        }

        return string.IsNullOrWhiteSpace(response.Content)
            ? ApiHealthStatus.Degraded
            : ApiHealthStatus.Healthy;
    }

    public static ApiManager CreateDefault()
    {
        return new ApiManager(new HttpService());
    }

    private static HttpRequestPolicy ResolvePolicy(string endpointId)
    {
        return endpointId.Equals("http_diagnostic", StringComparison.OrdinalIgnoreCase) ||
            endpointId.Contains("diagnostic", StringComparison.OrdinalIgnoreCase)
            ? HttpRequestPolicy.Diagnostics
            : HttpRequestPolicy.RemoteTool;
    }

    private static string FriendlyTimeoutMessage(TimeSpan timeout)
        => $"Remote service response timed out after {timeout.TotalSeconds:0} seconds. Please retry later or check your network/proxy settings.";

    private Task WriteFailureLogAsync(string endpointName, ApiEndpoint? endpoint, string error, CancellationToken cancellationToken)
    {
        var detail = endpoint is { ShouldHideEndpoint: true } ? "Sensitive endpoint hidden." : SensitiveText.Sanitize(error);
        return logService?.WriteAsync("Error", "api", $"{endpointName} request failed", detail, cancellationToken) ?? Task.CompletedTask;
    }
}
