using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using OF_DL.Models;
using OF_DL.Models.Config;
using OF_DL.Models.OfdlApi;
using OF_DL.Services;

namespace OF_DL.Tests.Services;

public class ApiServiceTests
{
    [Fact]
    public void GetDynamicHeaders_ReturnsSignedHeaders()
    {
        FakeAuthService authService = new()
        {
            CurrentAuth = new Auth
            {
                UserId = "123", UserAgent = "unit-test-agent", XBc = "xbc-token", Cookie = "auth_cookie=abc;"
            }
        };
        ApiService service = CreateService(authService);
        DynamicRules rules = new()
        {
            AppToken = "app-token",
            StaticParam = "static",
            Prefix = "prefix",
            Suffix = "suffix",
            ChecksumConstant = 7,
            ChecksumIndexes = [0, 5, 10, 15]
        };

        using DynamicRulesCacheScope _ = new(rules);

        Dictionary<string, string> headers = service.GetDynamicHeaders("/api2/v2/users", "?limit=1");

        Assert.Equal("application/json, text/plain", headers["accept"]);
        Assert.Equal("app-token", headers["app-token"]);
        Assert.Equal("auth_cookie=abc;", headers["cookie"]);
        Assert.Equal("unit-test-agent", headers["user-agent"]);
        Assert.Equal("xbc-token", headers["x-bc"]);
        Assert.Equal("123", headers["user-id"]);
        Assert.True(long.TryParse(headers["time"], out long timestamp));

        string expectedSign = BuildSign(rules, timestamp, "/api2/v2/users?limit=1", "123");
        Assert.Equal(expectedSign, headers["sign"]);
    }

    [Fact]
    public void GetDynamicHeaders_ThrowsWhenRulesInvalid()
    {
        FakeAuthService authService = new()
        {
            CurrentAuth = new Auth
            {
                UserId = "123", UserAgent = "unit-test-agent", XBc = "xbc-token", Cookie = "auth_cookie=abc;"
            }
        };
        ApiService service = CreateService(authService);
        DynamicRules rules = new()
        {
            AppToken = null,
            StaticParam = "static",
            Prefix = null,
            Suffix = "suffix",
            ChecksumConstant = null,
            ChecksumIndexes = []
        };

        using DynamicRulesCacheScope _ = new(rules);

        Exception ex = Assert.Throws<Exception>(() => service.GetDynamicHeaders("/api2/v2/users", "?limit=1"));
        Assert.Contains("Invalid dynamic rules", ex.Message);
    }

    [Fact]
    public void GetDynamicHeaders_ThrowsWhenAuthMissingFields()
    {
        FakeAuthService authService = new()
        {
            CurrentAuth = new Auth
            {
                UserId = "123", UserAgent = "unit-test-agent", XBc = "xbc-token", Cookie = null
            }
        };
        ApiService service = CreateService(authService);
        DynamicRules rules = new()
        {
            AppToken = "app-token",
            StaticParam = "static",
            Prefix = "prefix",
            Suffix = "suffix",
            ChecksumConstant = 1,
            ChecksumIndexes = [0]
        };

        using DynamicRulesCacheScope _ = new(rules);

        Exception ex = Assert.Throws<Exception>(() => service.GetDynamicHeaders("/api2/v2/users", "?limit=1"));
        Assert.Contains("Auth service is missing required fields", ex.Message);
    }

    private static ApiService CreateService(FakeAuthService authService) =>
        new(authService, new FakeConfigService(new Config()), new FakeDbService());

    private static string BuildSign(DynamicRules rules, long timestamp, string pathWithQuery, string userId)
    {
        string input = $"{rules.StaticParam}\n{timestamp}\n{pathWithQuery}\n{userId}";
        byte[] hashBytes = SHA1.HashData(Encoding.UTF8.GetBytes(input));
        string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();

        Assert.NotNull(rules.ChecksumConstant);

        int checksum = rules.ChecksumIndexes.Aggregate(0, (current, index) => current + hashString[index]) +
                       rules.ChecksumConstant.Value;
        string checksumHex = checksum.ToString("X").ToLowerInvariant();

        return $"{rules.Prefix}:{hashString}:{checksumHex}:{rules.Suffix}";
    }

    private sealed class DynamicRulesCacheScope : IDisposable
    {
        private static readonly FieldInfo s_rulesField =
            typeof(ApiService).GetField("s_cachedDynamicRules", BindingFlags.NonPublic | BindingFlags.Static) ??
            throw new InvalidOperationException("Unable to access cached rules field.");

        private static readonly FieldInfo s_expirationField =
            typeof(ApiService).GetField("s_cachedDynamicRulesExpiration",
                BindingFlags.NonPublic | BindingFlags.Static) ??
            throw new InvalidOperationException("Unable to access cached rules expiration field.");

        private readonly object? _priorRules;
        private readonly DateTime? _priorExpiration;

        public DynamicRulesCacheScope(DynamicRules rules)
        {
            _priorRules = s_rulesField.GetValue(null);
            _priorExpiration = (DateTime?)s_expirationField.GetValue(null);

            s_rulesField.SetValue(null, rules);
            s_expirationField.SetValue(null, DateTime.UtcNow.AddHours(1));
        }

        public void Dispose()
        {
            s_rulesField.SetValue(null, _priorRules);
            s_expirationField.SetValue(null, _priorExpiration);
        }
    }
}
