using System.Net;
using System.Net.Http.Headers;
using System.Reflection;
using GameList.Infrastructure;
using GameList.Endpoints;
using GameList.Tests.Support;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using System.Text.Json;
using System.Net.Http.Json;

namespace GameList.Tests;

public class HelperTests
{
    [Fact]
    public void PasswordHasher_roundtrip_and_empty_guard()
    {
        var (hash, salt) = PasswordHasher.HashPassword("secret");
        Assert.True(PasswordHasher.Verify("secret", hash, salt));
        Assert.False(PasswordHasher.Verify("other", hash, salt));
        Assert.Throws<ArgumentException>(() => PasswordHasher.HashPassword(""));
    }

    [Fact]
    public void UpdateIndexMetaBase_rewrites_content_value()
    {
        var webRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
        Directory.CreateDirectory(webRoot);
        var index = Path.Combine(webRoot, "index.html");
        File.WriteAllText(index, "<meta name=\"app-base\" content=\"\">");

        var env = new FakeEnv { WebRootPath = webRoot };
        var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("UpdateIndexMetaBase"));
        method.Invoke(null, [env, "/pick"]);

        var text = File.ReadAllText(index);
        Assert.Contains("content=\"/pick\"", text);
    }

    [Fact]
    public void UpdateIndexMetaBase_no_marker_no_change()
    {
        var webRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
        Directory.CreateDirectory(webRoot);
        var index = Path.Combine(webRoot, "index.html");
        File.WriteAllText(index, "<html></html>");

        var env = new FakeEnv { WebRootPath = webRoot };
        var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("UpdateIndexMetaBase"));
        method.Invoke(null, [env, "/pick"]);

        Assert.Equal("<html></html>", File.ReadAllText(index));
    }

    [Fact]
    public async Task IsReachableImageAsync_rejects_redirect_and_accepts_image()
    {
        Assert.True(await EndpointHelpers.IsReachableImageAsync(null, new StubHttpClientFactory(new StubHttpMessageHandler())));
        Assert.False(await EndpointHelpers.IsReachableImageAsync("http://127.0.0.1/img.png", new StubHttpClientFactory(new StubHttpMessageHandler())));
    }

    [Fact]
    public async Task IsReachableImageAsync_handles_head_success_redirect_and_size_guard()
    {
        var handler = new StubHttpMessageHandler();
        handler.SetResponder(req =>
        {
            if (req.Method == HttpMethod.Head)
            {
                var resp = new HttpResponseMessage(HttpStatusCode.OK);
                resp.Content = new ByteArrayContent([]);
                resp.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
                resp.Content.Headers.ContentLength = 100;
                return resp;
            }

            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new ByteArrayContent([])
                {
                    Headers =
                    {
                        ContentType = new MediaTypeHeaderValue("image/png"),
                        ContentLength = 100
                    }
                }
            };
        });

        var ok = await EndpointHelpers.IsReachableImageAsync("http://example.com/img.png", new StubHttpClientFactory(handler), handler);
        Assert.True(ok);

        handler.SetResponder(_ =>
        {
            var resp = new HttpResponseMessage(HttpStatusCode.Redirect);
            resp.Headers.Location = new Uri("http://example.com/other");
            return resp;
        });
        var redirect = await EndpointHelpers.IsReachableImageAsync("http://example.com/img.png", new StubHttpClientFactory(handler), handler);
        Assert.False(redirect);

        handler.SetResponder(_ =>
        {
            var resp = new HttpResponseMessage(HttpStatusCode.OK);
            resp.Content = new ByteArrayContent(new byte[10]);
            resp.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
            resp.Content.Headers.ContentLength = 6 * 1024 * 1024; // over 5 MB
            return resp;
        });
        var tooLarge = await EndpointHelpers.IsReachableImageAsync("http://example.com/img.png", new StubHttpClientFactory(handler), handler);
        Assert.False(tooLarge);
    }

    [Fact]
    public async Task IsReachableImageAsync_rejects_non_image_content()
    {
        var handler = new StubHttpMessageHandler();
        handler.SetResponder(_ =>
        {
            var resp = new HttpResponseMessage(HttpStatusCode.OK);
            resp.Content = new ByteArrayContent("not image"u8.ToArray());
            resp.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
            resp.Content.Headers.ContentLength = 9;
            return resp;
        });

        var result = await EndpointHelpers.IsReachableImageAsync("http://example.com/img.png", new StubHttpClientFactory(handler), handler);
        Assert.False(result);
    }

    [Fact]
    public void Link_root_helpers_handle_groups()
    {
        var roots = EndpointHelpers.BuildLinkRoots([(1, null), (2, 1), (3, null)]);
        Assert.Equal(1, roots[1]);
        Assert.Equal(1, roots[2]);
        Assert.Equal(3, roots[3]);

        var linked = EndpointHelpers.LinkedIdsFor(2, roots);
        Assert.Contains(1, linked);
        Assert.Contains(2, linked);
    }

    [Fact]
    public void Url_validation_rules()
    {
        Assert.True(EndpointHelpers.IsValidImageUrl("https://x.com/img.png"));
        Assert.False(EndpointHelpers.IsValidImageUrl("ftp://x/img.png"));
        Assert.True(EndpointHelpers.IsValidHttpUrl("http://x"));
        Assert.False(EndpointHelpers.IsValidHttpUrl("file://x"));
    }

    [Fact]
    public void Find_root_handles_cycles()
    {
        var parentMap = new Dictionary<int, int?>
        {
            { 1, 2 },
            { 2, 3 },
            { 3, 1 }
        };

        var root = EndpointHelpers.FindRootId(1, parentMap);
        Assert.Equal(3, root); // cycle breaks on revisit
    }

    [Fact]
    public async Task Global_exception_handler_returns_json_error()
    {
        await using var factory = new TestWebApplicationFactory().WithWebHostBuilder(builder =>
        {
            builder.Configure(app =>
            {
                app.UseGlobalExceptionLogging();
                app.UseRouting();
                app.UseEndpoints(endpoints => { endpoints.MapGet("/boom", _ => throw new InvalidOperationException("boom")); });
            });
        });

        var client = factory.CreateClient();
        var resp = await client.GetAsync("/boom");
        Assert.Equal(HttpStatusCode.InternalServerError, resp.StatusCode);
        var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
        Assert.Equal("Unexpected server error", json.GetProperty("error").GetString());
    }

    private class FakeEnv : IWebHostEnvironment
    {
        public string ApplicationName { get; set; } = "";
        public IFileProvider WebRootFileProvider { get; set; } = null!;
        public string WebRootPath { get; set; } = "";
        public string EnvironmentName { get; set; } = "";
        public string ContentRootPath { get; set; } = "";
        public IFileProvider ContentRootFileProvider { get; set; } = null!;
    }
}
