using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using Tiku.Api.Contracts;
using Tiku.Api.Options;
using Tiku.Domain.Identity;
using Tiku.Domain.Tenancy;

namespace Tiku.IntegrationTests.Api;

public sealed class BrowserAuthenticationTests
{
    [Fact]
    public async Task Password_login_uses_secure_cookies_and_does_not_return_tokens()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        const string phone = "13890000000";
        var user = new User { Id = Guid.NewGuid(), Phone = phone, Name = "Browser User" }.WithTestPassword();
        await factory.SeedAsync(
            new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Browser Tenant" },
            user,
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = user.Id,
                Role = TenantRole.Student,
                Status = MembershipStatus.Active
            });
        using var client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            HandleCookies = false
        });
        client.DefaultRequestHeaders.Add("x-tenant-code", tenantId.ToString("N"));
        client.DefaultRequestHeaders.Add("Origin", "http://localhost");

        var response = await client.PostAsJsonAsync("/api/tenant/auth/browser/login/password", new PasswordLoginDto
        {
            Realm = AuthRealm.Tenant,
            TenantCode = tenantId.ToString("N"),
            Identifier = phone,
            Password = PasswordTestUserExtensions.TestPassword
        });
        var body = await response.Content.ReadAsStringAsync();
        using var json = JsonDocument.Parse(body);
        var cookies = response.Headers.GetValues("Set-Cookie").ToArray();

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.DoesNotContain("accessToken", body, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("refreshToken", body, StringComparison.OrdinalIgnoreCase);
        Assert.Equal("Authenticated", json.RootElement.GetProperty("status").GetString());
        Assert.Contains(cookies, value => value.StartsWith(BrowserAuthOptions.AccessCookie) &&
                                          value.Contains("secure", StringComparison.OrdinalIgnoreCase) &&
                                          value.Contains("httponly", StringComparison.OrdinalIgnoreCase));
        Assert.Contains(cookies, value => value.StartsWith(BrowserAuthOptions.RefreshCookie) &&
                                          value.Contains("samesite=strict", StringComparison.OrdinalIgnoreCase));
        Assert.Contains(cookies, value => value.StartsWith(BrowserAuthOptions.CsrfCookie));
    }

    [Fact]
    public async Task Browser_refresh_requires_same_origin_and_matching_csrf_token()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        const string phone = "13890000001";
        var user = new User { Id = Guid.NewGuid(), Phone = phone, Name = "CSRF User" }.WithTestPassword();
        await factory.SeedAsync(
            new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "CSRF Tenant" },
            user,
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = user.Id,
                Role = TenantRole.Student,
                Status = MembershipStatus.Active
            });
        using var client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            HandleCookies = false
        });
        client.DefaultRequestHeaders.Add("x-tenant-code", tenantId.ToString("N"));
        using var login = new HttpRequestMessage(HttpMethod.Post, "/api/tenant/auth/browser/login/password")
        {
            Content = JsonContent.Create(new PasswordLoginDto
            {
                Realm = AuthRealm.Tenant,
                TenantCode = tenantId.ToString("N"),
                Identifier = phone,
                Password = PasswordTestUserExtensions.TestPassword
            })
        };
        login.Headers.Add("Origin", "http://localhost");
        var loginResponse = await client.SendAsync(login);
        var setCookies = loginResponse.Headers.GetValues("Set-Cookie").ToArray();
        var refresh = ReadCookie(setCookies, BrowserAuthOptions.RefreshCookie);
        var csrf = ReadCookie(setCookies, BrowserAuthOptions.CsrfCookie);
        var cookieHeader = $"{BrowserAuthOptions.RefreshCookie}={refresh}; {BrowserAuthOptions.CsrfCookie}={csrf}";

        var missingCsrf = await SendRefreshAsync(client, cookieHeader, "http://localhost", null);
        var wrongOrigin = await SendRefreshAsync(client, cookieHeader, "https://attacker.example", csrf);
        var accepted = await SendRefreshAsync(client, cookieHeader, "http://localhost", csrf);

        Assert.Equal(HttpStatusCode.Forbidden, missingCsrf.StatusCode);
        Assert.Equal(HttpStatusCode.Forbidden, wrongOrigin.StatusCode);
        Assert.Equal(HttpStatusCode.OK, accepted.StatusCode);
        Assert.Contains(accepted.Headers.GetValues("Set-Cookie"), value =>
            value.StartsWith(BrowserAuthOptions.RefreshCookie, StringComparison.Ordinal));
    }

    [Fact]
    public async Task Access_cookie_is_ignored_without_same_origin_evidence()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        const string phone = "13890000002";
        var user = new User { Id = Guid.NewGuid(), Phone = phone, Name = "Cookie User" }.WithTestPassword();
        await factory.SeedAsync(
            new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Cookie Tenant" },
            user,
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = user.Id,
                Role = TenantRole.Student,
                Status = MembershipStatus.Active
            });
        using var client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            HandleCookies = false
        });
        client.DefaultRequestHeaders.Add("x-tenant-code", tenantId.ToString("N"));
        using var login = new HttpRequestMessage(HttpMethod.Post, "/api/tenant/auth/browser/login/password")
        {
            Content = JsonContent.Create(new PasswordLoginDto
            {
                Realm = AuthRealm.Tenant,
                TenantCode = tenantId.ToString("N"),
                Identifier = phone,
                Password = PasswordTestUserExtensions.TestPassword
            })
        };
        login.Headers.Add("Origin", "http://localhost");
        var loginResponse = await client.SendAsync(login);
        var access = ReadCookie(loginResponse.Headers.GetValues("Set-Cookie").ToArray(),
            BrowserAuthOptions.AccessCookie);

        using var crossSite = new HttpRequestMessage(HttpMethod.Get, "/api/tenant/me");
        crossSite.Headers.Add("Cookie", $"{BrowserAuthOptions.AccessCookie}={access}");
        crossSite.Headers.Add("Sec-Fetch-Site", "cross-site");
        var rejected = await client.SendAsync(crossSite);
        using var sameOrigin = new HttpRequestMessage(HttpMethod.Get, "/api/tenant/me");
        sameOrigin.Headers.Add("Cookie", $"{BrowserAuthOptions.AccessCookie}={access}");
        sameOrigin.Headers.Add("Origin", "http://localhost");
        var accepted = await client.SendAsync(sameOrigin);

        Assert.Equal(HttpStatusCode.Unauthorized, rejected.StatusCode);
        Assert.Equal(HttpStatusCode.OK, accepted.StatusCode);
    }

    private static async Task<HttpResponseMessage> SendRefreshAsync(
        HttpClient client,
        string cookieHeader,
        string origin,
        string? csrf)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/api/tenant/auth/browser/refresh");
        request.Headers.Add("Cookie", cookieHeader);
        request.Headers.Add("Origin", origin);
        if (csrf is not null) request.Headers.Add(BrowserAuthOptions.CsrfHeader, csrf);
        return await client.SendAsync(request);
    }

    private static string ReadCookie(IEnumerable<string> setCookies, string name)
    {
        var prefix = name + "=";
        var header = setCookies.Single(value => value.StartsWith(prefix, StringComparison.Ordinal));
        return header[prefix.Length..header.IndexOf(';')];
    }
}