using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using ytLive.Models;
using ytLive.Services;

namespace ytLive.Tests;

public class SocialValidatorTests
{
    private sealed class StubHandler : HttpMessageHandler
    {
        private readonly Func<HttpRequestMessage, HttpResponseMessage> _respond;

        public StubHandler(HttpResponseMessage response) : this(_ => response) { }

        public StubHandler(Func<HttpRequestMessage, HttpResponseMessage> respond) => _respond = respond;

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
        {
            if (ct.IsCancellationRequested)
                return Task.FromCanceled<HttpResponseMessage>(ct);
            return Task.FromResult(_respond(request));
        }
    }

    private static HttpSocialValidator Validator(HttpMessageHandler handler)
        => new(new HttpClient(handler));

    [Fact]
    public async Task FediverseHandle_BuildsHostUrl()
    {
        var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.OK)));

        var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", CancellationToken.None);

        Assert.True(result.Success);
        Assert.Equal("https://site.tld/@user", result.ProfileUrl);
        Assert.Equal("@user@site.tld", result.Handle);
    }

    [Fact]
    public async Task FediverseHandle_FetchesInstanceSoftware()
    {
        var validator = Validator(new StubHandler(RespondFediverse("peertube")));

        var result = await validator.LookupAsync(SocialService.Fediverse, "@gramps@llamachile.tube", CancellationToken.None);

        Assert.True(result.Success);
        Assert.Equal("peertube", result.FediverseSoftware);
    }

    [Fact]
    public async Task FediverseHandle_RootRedirectToSubdomain_ResolvesSoftware()
    {
        var validator = Validator(new StubHandler(request =>
        {
            var uri = request.RequestUri!;
            if (uri.Host == "site.tld")
            {
                if (uri.AbsolutePath == "/")
                {
                    // Identity domain's default app redirects to the real instance.
                    return new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        RequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://social.site.tld/"),
                    };
                }
                if (uri.AbsolutePath.StartsWith("/@"))
                    return new HttpResponseMessage(HttpStatusCode.OK); // profile validates
                return new HttpResponseMessage(HttpStatusCode.NotFound); // nodeinfo SSO-blocked
            }
            if (uri.Host == "social.site.tld")
            {
                if (uri.AbsolutePath.StartsWith("/.well-known"))
                    return Json(new { links = new[] { new { rel = "http://nodeinfo.diaspora.software/ns/schema/2.0", href = "https://social.site.tld/nodeinfo/2.0" } } });
                if (uri.AbsolutePath.StartsWith("/nodeinfo"))
                    return Json(new { software = new { name = "mastodon", version = "4.6.3" } });
            }
            return new HttpResponseMessage(HttpStatusCode.OK);
        }));

        var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", CancellationToken.None);

        Assert.True(result.Success);
        Assert.Equal("mastodon", result.FediverseSoftware);
    }

    [Fact]
    public async Task FediverseHandle_NodeInfoUnavailable_StillSucceeds()
    {
        var validator = Validator(new StubHandler(request =>
            request.RequestUri!.AbsolutePath.StartsWith("/.well-known")
                ? new HttpResponseMessage(HttpStatusCode.NotFound)
                : new HttpResponseMessage(HttpStatusCode.OK)));

        var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", CancellationToken.None);

        Assert.True(result.Success);
        Assert.Null(result.FediverseSoftware);
    }

    [Fact]
    public async Task FediverseSoftware_IdentityDomainSilent_ProbesWellKnownSubdomains()
    {
        // The identity domain is a silent landing page (no nodeinfo, no redirect);
        // the real instance lives on mastodon.<domain> — the probe must find it.
        var validator = Validator(new StubHandler(request =>
        {
            var host = request.RequestUri!.Host;
            if (host == "mastodon.llamachile.tube")
            {
                if (request.RequestUri.AbsolutePath.StartsWith("/.well-known"))
                    return Json(new { links = new[] { new { rel = "http://nodeinfo.diaspora.software/ns/schema/2.0", href = "https://mastodon.llamachile.tube/nodeinfo/2.0" } } });
                if (request.RequestUri.AbsolutePath.StartsWith("/nodeinfo"))
                    return Json(new { software = new { name = "mastodon", version = "4.6.3" } });
            }
            return new HttpResponseMessage(HttpStatusCode.NotFound);
        }));

        var software = await validator.ResolveFediverseSoftwareAsync("llamachile.tube", CancellationToken.None);

        Assert.Equal("mastodon", software);
    }

    [Fact]
    public async Task FediverseSoftware_NoSubdomainAnswers_ReturnsNull()
    {
        var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.NotFound)));

        var software = await validator.ResolveFediverseSoftwareAsync("silent.example", CancellationToken.None);

        Assert.Null(software);
    }

    [Fact]
    public async Task FediverseHandle_CanceledDuringNodeInfo_IsCanceled()
    {
        using var cts = new CancellationTokenSource();
        var request = 0;
        var validator = Validator(new StubHandler(_ =>
        {
            if (++request >= 2) cts.Cancel();
            return new HttpResponseMessage(HttpStatusCode.OK);
        }));

        var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", cts.Token);

        Assert.True(result.Canceled);
        Assert.False(result.Success);
    }

    private static Func<HttpRequestMessage, HttpResponseMessage> RespondFediverse(string software)
    {
        return request =>
        {
            if (request.RequestUri!.AbsolutePath.StartsWith("/.well-known"))
            {
                return Json(new
                {
                    links = new[]
                    {
                        new
                        {
                            rel = "http://nodeinfo.diaspora.software/ns/schema/2.0",
                            href = $"https://{request.RequestUri.Host}/nodeinfo/2.0",
                        },
                    },
                });
            }
            if (request.RequestUri!.AbsolutePath.StartsWith("/nodeinfo"))
            {
                return Json(new { software = new { name = software, version = "1.0.0" } });
            }
            return new HttpResponseMessage(HttpStatusCode.OK);
        };
    }

    private static HttpResponseMessage Json(object payload)
        => new(HttpStatusCode.OK) { Content = new StringContent(System.Text.Json.JsonSerializer.Serialize(payload)) };

    [Fact]
    public async Task KnownServiceHandle_CanonicalUrl()
    {
        var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.OK)));

        var result = await validator.LookupAsync(SocialService.X, "creator", CancellationToken.None);

        Assert.True(result.Success);
        Assert.Equal("https://x.com/creator", result.ProfileUrl);
        Assert.Equal("creator", result.Handle);
    }

    [Fact]
    public async Task NotFound_IsRejected()
    {
        var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.NotFound)));

        var result = await validator.LookupAsync(SocialService.X, "doesnotexist", CancellationToken.None);

        Assert.False(result.Success);
        Assert.Contains("NotFound", result.Error);
    }

    [Fact]
    public async Task Redirect_CountsAsExists()
    {
        var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.Found)));

        var result = await validator.LookupAsync(SocialService.GitHub, "dev", CancellationToken.None);

        Assert.True(result.Success);
    }

    [Fact]
    public async Task ConnectionFailure_IsFriendlyError()
    {
        var validator = Validator(new StubHandler(_ => throw new HttpRequestException("boom")));

        var result = await validator.LookupAsync(SocialService.X, "user", CancellationToken.None);

        Assert.False(result.Success);
        Assert.Contains("Couldn't reach", result.Error);
    }

    [Fact]
    public async Task CanceledToken_AbortsRequest()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
        var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.OK)));

        var result = await validator.LookupAsync(SocialService.X, "creator", cts.Token);

        Assert.True(result.Canceled);
        Assert.False(result.Success);
    }
}
