using System.Net;
using UniVerse.Api.Tests.Helpers;
using Xunit;

namespace UniVerse.Api.Tests.Authorization;

/// <summary>
/// Интеграционные тесты для ролевого контроля доступа ко всем конечным точкам API.
///
/// Каждый тестовый случай представляет собой кортеж:
///   (description, method, url, requiredRole, forbiddenRoles[])
///
/// Три типа сценариев для каждой конечной точки:
///   A) Анонимный → 401 Unauthorized
///   B) Неправильная роль → 403 Forbidden
///   C) Правильная роль → не 401 / не 403 (зависит от бизнес-логики: успех или доменная ошибка)
/// </summary>
public class EndpointAuthorizationTests : IClassFixture<ApiWebApplicationFactory>
{
    private readonly HttpClient _client;

    public EndpointAuthorizationTests(ApiWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Тестовые данные
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Конечные точки, требующие аутентификации (не анонимные).
    /// Формат: (description, method, url, correctRole, forbiddenRoles[])
    ///
    /// "AnyAuth" означает, что достаточно любого валидного JWT — без ограничения по роли.
    /// Для конечных точек с несколькими ролями (Admin,Teacher) обе роли указаны как правильные.
    /// </summary>
    public static IEnumerable<object[]> AuthenticatedEndpoints()
    {
        // ── Auth ─────────────────────────────────────────────────────────────
        yield return E("auth/logout [AnyAuth]",            "POST",   "api/v1/auth/logout",    "Student");
        yield return E("auth/me [AnyAuth]",                "GET",    "api/v1/auth/me",        "Student");

        // ── Users — current user ──────────────────────────────────────────────
        yield return E("users/me GET [AnyAuth]",           "GET",    "api/v1/users/me",       "Student");
        yield return E("users/me PUT [AnyAuth]",           "PUT",    "api/v1/users/me",       "Student",
            body: """{"displayName":"Test","avatarUrl":null}""");
        yield return E("users/me/stats [AnyAuth]",         "GET",    "api/v1/users/me/stats", "Student");
        yield return E("users/me/enrollments [AnyAuth]",   "GET",    "api/v1/users/me/enrollments", "Student");
        yield return E("users/me/enrollments/calendar-subscription [AnyAuth]", "GET", "api/v1/users/me/enrollments/calendar-subscription", "Student");
        yield return E("users/me/enrollments.ics [AnyAuth]", "GET",  "api/v1/users/me/enrollments.ics", "Student");
        yield return E("users/me/enrollments/{id}.ics [AnyAuth]", "GET", "api/v1/users/me/enrollments/1.ics", "Student");
        yield return E("users/me/reviews [AnyAuth]",       "GET",    "api/v1/users/me/reviews", "Student");
        yield return E("users/me/achievements [AnyAuth]",  "GET",    "api/v1/users/me/achievements", "Student");
        yield return E("users/me/transactions [AnyAuth]",  "GET",    "api/v1/users/me/transactions", "Student");

        // ── Users — Admin only ────────────────────────────────────────────────
        yield return E("users GET [Admin]",                "GET",    "api/v1/users",          "Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("users/{id} GET [Admin]",           "GET",    "api/v1/users/1",        "Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("users/{id} PUT [Admin]",           "PUT",    "api/v1/users/1",        "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"displayName":"Test","avatarUrl":null}""");
        yield return E("users/{id}/stats [Admin]",         "GET",    "api/v1/users/1/stats",  "Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("users/{id}/enrollments [Admin]",   "GET",    "api/v1/users/1/enrollments", "Admin", forbidden: ["Student", "Teacher"]);
        yield return E("users/{id}/reviews [Admin]",       "GET",    "api/v1/users/1/reviews","Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("users/{id}/achievements [Admin]",  "GET",    "api/v1/users/1/achievements","Admin", forbidden: ["Student", "Teacher"]);
        yield return E("users/{id}/transactions [Admin]",  "GET",    "api/v1/users/1/transactions","Admin", forbidden: ["Student", "Teacher"]);
        yield return E("users/{id}/role PATCH [Admin]",    "PATCH",  "api/v1/users/1/role",   "Admin",   forbidden: ["Student", "Teacher"],
            body: "\"Student\"");
        yield return E("users/{id}/active PATCH [Admin]",  "PATCH",  "api/v1/users/1/active", "Admin",   forbidden: ["Student", "Teacher"],
            body: "true");

        // ── Courses — any auth ────────────────────────────────────────────────
        yield return E("courses GET [AnyAuth]",            "GET",    "api/v1/courses",        "Student");
        yield return E("courses/{id} GET [AnyAuth]",       "GET",    "api/v1/courses/1",      "Student");

        // ── Courses — Admin only ──────────────────────────────────────────────
        yield return E("courses POST [Admin]",             "POST",   "api/v1/courses",        "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Course","description":null}""");
        yield return E("courses/{id} PUT [Admin]",         "PUT",    "api/v1/courses/1",      "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Course","description":null}""");
        yield return E("courses/{id} DELETE [Admin]",      "DELETE", "api/v1/courses/1",      "Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("courses/{id}/tags POST [Admin]",   "POST",   "api/v1/courses/1/tags", "Admin",   forbidden: ["Student", "Teacher"],
            body: "1");
        yield return E("courses/{id}/tags/{tagId} DELETE [Admin]","DELETE","api/v1/courses/1/tags/1","Admin",forbidden: ["Student", "Teacher"]);

        // ── Lectures — any auth ───────────────────────────────────────────────
        yield return E("lectures GET [AnyAuth]",           "GET",    "api/v1/lectures",       "Student");
        yield return E("lectures/{id} GET [AnyAuth]",      "GET",    "api/v1/lectures/1",     "Student");

        // ── Lectures — Admin only ─────────────────────────────────────────────
        yield return E("lectures POST [Admin]",            "POST",   "api/v1/lectures",       "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"courseId":1,"teacherId":null,"locationId":null,"title":"T","description":null,"format":"Offline","startsAt":"2026-09-01T10:00:00Z","endsAt":"2026-09-01T12:00:00Z","isOpen":true,"maxEnrollments":30,"onlineUrl":null}""");
        yield return E("lectures/{id} DELETE [Admin]",     "DELETE", "api/v1/lectures/1",     "Admin",   forbidden: ["Student", "Teacher"]);

        // ── Lectures — Admin OR Teacher ───────────────────────────────────────
        yield return E("lectures/{id} PUT [Admin]",        "PUT",    "api/v1/lectures/1",     "Admin",   forbidden: ["Student"],
            body: """{"teacherId":null,"locationId":null,"title":"T","description":null,"format":"Offline","startsAt":"2026-09-01T10:00:00Z","endsAt":"2026-09-01T12:00:00Z","isOpen":true,"maxEnrollments":30,"onlineUrl":null}""");
        yield return E("lectures/{id} PUT [Teacher]",      "PUT",    "api/v1/lectures/1",     "Teacher", forbidden: ["Student"],
            body: """{"teacherId":null,"locationId":null,"title":"T","description":null,"format":"Offline","startsAt":"2026-09-01T10:00:00Z","endsAt":"2026-09-01T12:00:00Z","isOpen":true,"maxEnrollments":30,"onlineUrl":null}""");
        yield return E("lectures/{id}/attendance PATCH [Admin]", "PATCH","api/v1/lectures/1/attendance/2","Admin",  forbidden: ["Student"],
            body: "true");
        yield return E("lectures/{id}/attendance PATCH [Teacher]","PATCH","api/v1/lectures/1/attendance/2","Teacher",forbidden: ["Student"],
            body: "true");
        yield return E("lectures/{id}/enrollments GET [Admin]",  "GET","api/v1/lectures/1/enrollments","Admin",  forbidden: ["Student"]);
        yield return E("lectures/{id}/enrollments GET [Teacher]","GET","api/v1/lectures/1/enrollments","Teacher",forbidden: ["Student"]);
        yield return E("lectures/{id}/reviews GET [Admin]",      "GET","api/v1/lectures/1/reviews","Admin",  forbidden: ["Student"]);
        yield return E("lectures/{id}/reviews GET [Teacher]",    "GET","api/v1/lectures/1/reviews","Teacher",forbidden: ["Student"]);

        // ── Lectures — Student only ───────────────────────────────────────────
        yield return E("lectures/{id}/enroll POST [Student]",   "POST",  "api/v1/lectures/1/enroll",  "Student", forbidden: ["Admin", "Teacher"]);
        yield return E("lectures/{id}/enroll DELETE [Student]", "DELETE","api/v1/lectures/1/enroll",  "Student", forbidden: ["Admin", "Teacher"]);

        // ── Reviews — any auth ────────────────────────────────────────────────
        yield return E("reviews/{id} PUT [AnyAuth]",       "PUT",    "api/v1/reviews/1",      "Student",
            body: """{"rating":"Like","text":"Updated"}""");
        yield return E("reviews/{id} DELETE [AnyAuth]",    "DELETE", "api/v1/reviews/1",      "Student");

        // ── Reviews — Admin OR Teacher ───────────────────────────────────────
        yield return E("reviews/{id} GET [Admin]",         "GET",    "api/v1/reviews/1",      "Admin",   forbidden: ["Student"]);
        yield return E("reviews/{id} GET [Teacher]",       "GET",    "api/v1/reviews/1",      "Teacher", forbidden: ["Student"]);

        // ── Reviews — Student only ────────────────────────────────────────────
        yield return E("reviews POST [Student]",           "POST",   "api/v1/reviews",        "Student", forbidden: ["Admin", "Teacher"],
            body: """{"lectureId":1,"rating":"Like","text":"Great lecture!"}""");

        // ── Reviews — Admin only ──────────────────────────────────────────────
        yield return E("reviews GET [Admin]",             "GET",    "api/v1/reviews",        "Admin", forbidden: ["Student", "Teacher"]);
        yield return E("reviews/llm-prompt GET [Admin]",  "GET",    "api/v1/reviews/llm-prompt","Admin", forbidden: ["Student", "Teacher"]);
        yield return E("reviews/llm-prompt PUT [Admin]",  "PUT",    "api/v1/reviews/llm-prompt","Admin", forbidden: ["Student", "Teacher"],
            body: """{"prompt":"Analyze {lectureContext}. Review: {reviewText}"}""");
        yield return E("reviews/{id}/reanalyze POST [Admin]","POST", "api/v1/reviews/1/reanalyze","Admin",forbidden: ["Student", "Teacher"]);

        // ── Tags — any auth ───────────────────────────────────────────────────
        yield return E("tags GET [AnyAuth]",               "GET",    "api/v1/tags",           "Student");
        yield return E("tags/{id} GET [AnyAuth]",          "GET",    "api/v1/tags/1",         "Student");
        yield return E("tags/tree GET [AnyAuth]",          "GET",    "api/v1/tags/tree",      "Student");

        // ── Tags — Admin only ─────────────────────────────────────────────────
        yield return E("tags POST [Admin]",                "POST",   "api/v1/tags",           "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Tag","type":"Topic","parentId":null}""");
        yield return E("tags/{id} PUT [Admin]",            "PUT",    "api/v1/tags/1",         "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Tag","type":"Topic","parentId":null}""");
        yield return E("tags/{id} DELETE [Admin]",         "DELETE", "api/v1/tags/1",         "Admin",   forbidden: ["Student", "Teacher"]);

        // ── Locations — any auth ──────────────────────────────────────────────
        yield return E("locations GET [AnyAuth]",          "GET",    "api/v1/locations",      "Student");
        yield return E("locations/{id} GET [AnyAuth]",     "GET",    "api/v1/locations/1",    "Student");

        // ── Locations — Admin only ────────────────────────────────────────────
        yield return E("locations POST [Admin]",           "POST",   "api/v1/locations",      "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Room 1","building":null,"room":null,"address":null}""");
        yield return E("locations/{id} PUT [Admin]",       "PUT",    "api/v1/locations/1",    "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Room 1","building":null,"room":null,"address":null}""");
        yield return E("locations/{id} DELETE [Admin]",    "DELETE", "api/v1/locations/1",    "Admin",   forbidden: ["Student", "Teacher"]);

        // ── Achievements — any auth ───────────────────────────────────────────
        yield return E("achievements GET [AnyAuth]",       "GET",    "api/v1/achievements",   "Student");
        yield return E("achievements/{id} GET [AnyAuth]",  "GET",    "api/v1/achievements/1", "Student");

        // ── Achievements — Admin only ─────────────────────────────────────────
        yield return E("achievements POST [Admin]",        "POST",   "api/v1/achievements",   "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Ach","description":null,"iconUrl":null,"xpReward":10,"coinReward":5,"condition":null}""");
        yield return E("achievements/{id} PUT [Admin]",    "PUT",    "api/v1/achievements/1", "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"name":"Ach","description":null,"iconUrl":null,"xpReward":10,"coinReward":5,"condition":null}""");
        yield return E("achievements/{id} DELETE [Admin]", "DELETE", "api/v1/achievements/1", "Admin",   forbidden: ["Student", "Teacher"]);

        // ── Sync — Admin only ─────────────────────────────────────────────────
        yield return E("sync/schedule POST [Admin]",       "POST",   "api/v1/sync/schedule",  "Admin",   forbidden: ["Student", "Teacher"],
            body: """{"specialtyCode":null,"timeMin":null,"timeMax":null,"typeId":null}""");
        yield return E("sync/status GET [Admin]",          "GET",    "api/v1/sync/status",    "Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("sync/rooms POST [Admin]",          "POST",   "api/v1/sync/rooms",     "Admin",   forbidden: ["Student", "Teacher"]);
        yield return E("sync/employees POST [Admin]",      "POST",   "api/v1/sync/employees?fullname=test","Admin",forbidden: ["Student", "Teacher"]);

        // ── Notifications — any auth ───────────────────────────────────────────
        yield return E("notifications GET [AnyAuth]", "GET", "api/v1/notifications", "Student");
        yield return E("notifications/read-all PATCH [AnyAuth]", "PATCH", "api/v1/notifications/read-all", "Student");

        // ── Notifications — Admin only ─────────────────────────────────────────
        yield return E("notifications/send POST [Admin]",  "POST",   "api/v1/notifications/send", "Admin", forbidden: ["Student", "Teacher"],
            body: """{"channel":"email","recipient":"user@test.com","subject":"Test","body":"Hello"}""");
        yield return E("notifications/schedule POST [Admin]", "POST", "api/v1/notifications/schedule", "Admin", forbidden: ["Student", "Teacher"],
            body: $$"""{"channel":"email","recipient":"user@test.com","subject":"Test","body":"Hello","sendAt":"{{DateTimeOffset.UtcNow.AddMinutes(5):O}}"}""");
    }

    /// <summary>
    /// Анонимные конечные точки — запросы без токена НЕ должны возвращать 401.
    /// (они могут делать перенаправление или возвращать 500 из-за отсутствия конфигурации, но не 401)
    /// </summary>
    public static IEnumerable<object[]> AnonymousEndpoints()
    {
        // login/microsoft GET перенаправляет на Microsoft — AzureAd настроен в фабрике
        yield return new object[] { "auth/login/microsoft GET", "GET", "api/v1/auth/login/microsoft" };
        // callback разрешает анонимный доступ — возвращает 400, если отсутствует параметр code
        yield return new object[] { "auth/callback/microsoft GET", "GET", "api/v1/auth/callback/microsoft" };
        // dev login доступен в окружении Development
        yield return new object[] { "auth/login/dev POST", "POST", "api/v1/auth/login/dev",
            """{"email":"test@test.com","displayName":"Test","role":"Student"}""" };
        yield return new object[] { "users/calendar/enrollments/{token}.ics GET", "GET", "api/v1/users/calendar/enrollments/bad-token.ics" };
        // refresh читает из cookie — возвращает 401, если нет cookie, но это не 401 от промежуточного ПО авторизации
        // (он возвращает 401 явно в теле действия, что отличается от Auth Challenge)
        // Мы тестируем это отдельно, чтобы убедиться, что заголовок JWT не требуется
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Тест: анонимный → 401
    // ─────────────────────────────────────────────────────────────────────────

    [Theory]
    [MemberData(nameof(AuthenticatedEndpoints))]
    public async Task Endpoint_Anonymous_Returns401(
        string description, string method, string url,
        string correctRole, string[] forbiddenRoles, string? body)
    {
        // Подготовка — без заголовка аутентификации
        var request = BuildRequest(method, url, body, authHeader: null);

        // Действие
        var response = await _client.SendAsync(request);

        // Проверка
        Assert.True(
            response.StatusCode == HttpStatusCode.Unauthorized,
            $"[{description}] Ожидался ответ 401 Unauthorized для анонимного запроса, получено {(int)response.StatusCode} {response.StatusCode}");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Тест: неправильная роль → 403
    // ─────────────────────────────────────────────────────────────────────────

    [Theory]
    [MemberData(nameof(AuthenticatedEndpoints))]
    public async Task Endpoint_WrongRole_Returns403(
        string description, string method, string url,
        string correctRole, string[] forbiddenRoles, string? body)
    {
        foreach (var forbidden in forbiddenRoles)
        {
            // Подготовка
            var request = BuildRequest(method, url, body,
                authHeader: TestJwtFactory.BearerHeader(forbidden));

            // Действие
            var response = await _client.SendAsync(request);

            // Проверка
            Assert.True(
                response.StatusCode == HttpStatusCode.Forbidden,
                $"[{description}] Ожидался ответ 403 Forbidden для роли '{forbidden}', получено {(int)response.StatusCode} {response.StatusCode}");
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Тест: правильная роль → не 401 и не 403
    // ─────────────────────────────────────────────────────────────────────────

    [Theory]
    [MemberData(nameof(AuthenticatedEndpoints))]
    public async Task Endpoint_CorrectRole_PassesAuthz(
        string description, string method, string url,
        string correctRole, string[] forbiddenRoles, string? body)
    {
        // Подготовка
        var request = BuildRequest(method, url, body,
            authHeader: TestJwtFactory.BearerHeader(correctRole));

        // Действие
        var response = await _client.SendAsync(request);

        // Проверка — принимается любой ответ, который НЕ 401/403
        Assert.True(
            response.StatusCode != HttpStatusCode.Unauthorized &&
            response.StatusCode != HttpStatusCode.Forbidden,
            $"[{description}] Роль '{correctRole}' должна успешно пройти авторизацию, получено {(int)response.StatusCode} {response.StatusCode}");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Тест: анонимные конечные точки не должны возвращать 401
    // ─────────────────────────────────────────────────────────────────────────

    [Theory]
    [MemberData(nameof(AnonymousEndpoints))]
    public async Task AnonymousEndpoint_NoToken_DoesNotReturn401(
        string description, string method, string url, string? body = null)
    {
        var request = BuildRequest(method, url, body, authHeader: null);
        var response = await _client.SendAsync(request);

        Assert.True(
            response.StatusCode != HttpStatusCode.Unauthorized,
            $"[{description}] Анонимная конечная точка не должна возвращать 401, получено {(int)response.StatusCode} {response.StatusCode}");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Вспомогательные методы
    // ─────────────────────────────────────────────────────────────────────────

    private static HttpRequestMessage BuildRequest(
        string method, string url, string? body, string? authHeader)
    {
        var request = new HttpRequestMessage(new HttpMethod(method), url);

        if (authHeader != null)
            request.Headers.Add("Authorization", authHeader);

        if (body != null)
            request.Content = new StringContent(body,
                System.Text.Encoding.UTF8, "application/json");

        return request;
    }

    /// <summary>Вспомогательный метод для компактного создания массивов объектов [MemberData].</summary>
    private static object[] E(
        string description,
        string method,
        string url,
        string correctRole,
        string[]? forbidden = null,
        string? body = null)
        => [description, method, url, correctRole, forbidden ?? [], body];
}
