using System.Collections.Specialized;
using System.Net;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
using YandexMusic.API.Common;
using YandexMusic.API.Extensions;
using YandexMusic.API.Requests.Common.Attributes;

namespace YandexMusic.API.Requests.Common;

/// <summary>Базовый строитель HTTP-запросов к API Яндекс.Музыки.</summary>
/// <typeparam name="TResponse">Тип ответа.</typeparam>
/// <typeparam name="TParams">Тип параметров запроса.</typeparam>
public abstract class YRequestBuilder<TResponse, TParams>
{
    private readonly YRequestAttribute _requestInfo;
    private Dictionary<string, string> _substitutions = null!;
    private readonly JsonSerializerOptions _jsonOptions;

    protected readonly YandexMusicApi api;
    protected readonly AuthStorage storage;
    protected string device;

    protected YRequestBuilder(YandexMusicApi yandex, AuthStorage auth)
    {
        _requestInfo = GetType().GetCustomAttribute<YRequestAttribute>()
            ?? throw new NotImplementedException($"Отсутствует атрибут {nameof(YRequestAttribute)}");
        api = yandex;
        storage = auth;
        device = $"os=CSharp; os_version=; manufacturer=K1llM@n; model=Yandex Music API; clid=; device_id={storage.DeviceId}; uuid=random";

        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
            Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
        };
    }

    private Uri BuildUri(TParams tuple)
    {
        var queryParams = GetQueryParams(tuple);
        var modifiedParams = HttpUtility.ParseQueryString(string.Empty);
        foreach (string? key in queryParams)
            if (key != null)
                modifiedParams[key] = ReplaceSubs(queryParams[key]!);
        var endpoint = ReplaceSubs(_requestInfo.Url);
        var builder = new UriBuilder(endpoint) { Query = modifiedParams.ToString() ?? string.Empty };
        return builder.Uri;
    }

    private HttpRequestMessage CreateMessage(TParams tuple)
    {
        var msg = new HttpRequestMessage
        {
            RequestUri = BuildUri(tuple),
            Method = new HttpMethod(_requestInfo.Method),
            Content = GetContent(tuple)
        };
        msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptCharset.GetName(), Encoding.UTF8.WebName);
        msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptEncoding.GetName(), "gzip");
        if (!string.IsNullOrEmpty(storage.Token))
            msg.Headers.TryAddWithoutValidation(HttpRequestHeader.Authorization.GetName(), $"OAuth {storage.Token}");
        SetCustomHeaders(msg.Headers);
        return msg;
    }

    protected string ReplaceSubs(string str)
    {
        var subs = str.GetMatches(@"\{.+?\}");
        foreach (var s in subs)
        {
            var key = s.ReplaceRegex(@"[\{\}]", string.Empty);
            if (!_substitutions.TryGetValue(key, out var value))
                throw new Exception($"Не найдена подстановка {s}");
            str = str.Replace(s, value);
        }
        return str;
    }

    protected virtual Dictionary<string, string> GetSubstitutions(TParams tuple) => [];
    protected virtual NameValueCollection GetQueryParams(TParams tuple) => [];
    protected virtual HttpContent? GetContent(TParams tuple) => null;
    protected virtual void SetCustomHeaders(HttpRequestHeaders headers) { }
    protected string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions);

    internal YRequest<TResponse> Build(TParams tuple)
    {
        _substitutions = GetSubstitutions(tuple);
        var msg = CreateMessage(tuple);
        return new YRequest<TResponse>(msg, api, storage);
    }
}