using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Contracts;
using Tiku.Domain.Catalog;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Identity;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class ContentManagementEndpointTests
{
    [Fact]
    public async Task Tenant_content_management_requires_admin_authentication()
    {
        await using var factory = new ApiTestFactory();
        using var client = factory.CreateClient();

        using var response = await client.GetAsync("/api/tenant/content/entries");

        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }

    [Fact]
    public async Task Tenant_admin_can_upsert_entries_and_nodes()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedAdminAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        using var entryResponse = await client.PostAsJsonAsync(
            "/api/tenant/content/entries",
            new UpsertContentEntryDto
            {
                EntryKey = "exam-practice",
                Name = "刷题入口",
                EntryType = "questionPractice",
                Visibility = "members",
                Order = 10
            });
        var entryJson = await ReadJsonAsync(entryResponse);
        var entryId = entryJson.RootElement.GetProperty("item").GetProperty("id").GetGuid();

        using var nodeResponse = await client.PostAsJsonAsync(
            "/api/tenant/content/nodes",
            new UpsertContentNodeDto
            {
                EntryId = entryId,
                NodeKey = "chapter-1",
                Name = "第一章",
                NodeType = "chapter",
                IsLeaf = true
            });
        var nodeJson = await ReadJsonAsync(nodeResponse);
        using var listResponse = await client.GetAsync($"/api/tenant/content/nodes?entryId={entryId}&parentId=root");
        var listJson = await ReadJsonAsync(listResponse);

        Assert.Equal(HttpStatusCode.OK, entryResponse.StatusCode);
        Assert.Equal("exam-practice", entryJson.RootElement.GetProperty("item").GetProperty("entryKey").GetString());
        Assert.Equal(HttpStatusCode.OK, nodeResponse.StatusCode);
        var node = nodeJson.RootElement.GetProperty("item");
        Assert.Equal(0, node.GetProperty("depth").GetInt32());
        Assert.StartsWith("n_", node.GetProperty("path").GetString(), StringComparison.Ordinal);
        Assert.Single(listJson.RootElement.GetProperty("items").EnumerateArray());
    }

    [Fact]
    public async Task Tenant_admin_can_upsert_collection_replace_items_and_create_blueprint()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedAdminAsync(factory);
        var questionId = Guid.NewGuid();
        await factory.SeedAsync(new Question
        {
            Id = questionId,
            TenantId = seed.TenantId,
            Type = "choice",
            Status = QuestionStatus.Published
        });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var entryId = await CreateEntryAsync(client);
        var collectionResponse = await client.PostAsJsonAsync(
            "/api/tenant/content/question-collections",
            new UpsertQuestionCollectionDto
            {
                EntryId = entryId,
                Name = "基础题集",
                CollectionType = "manual",
                SourceType = "manualQuestions",
                TotalScore = 100,
                DurationMinutes = 45
            });
        var collectionJson = await ReadJsonAsync(collectionResponse);
        var collectionId = collectionJson.RootElement.GetProperty("item").GetProperty("id").GetGuid();

        var replaceResponse = await client.PostAsJsonAsync(
            "/api/tenant/content/question-collections/items/replace",
            new ReplaceCollectionItemsDto
            {
                CollectionId = collectionId,
                Questions =
                [
                    new CollectionQuestionDto
                    {
                        QuestionId = questionId,
                        SectionKey = "choice",
                        Order = 1,
                        Score = 5,
                        Required = true
                    }
                ]
            });
        var replaceJson = await ReadJsonAsync(replaceResponse);

        var blueprintResponse = await client.PostAsJsonAsync(
            "/api/tenant/content/practice-blueprints",
            new UpsertPracticeBlueprintDto
            {
                EntryId = entryId,
                CollectionId = collectionId,
                Name = "章节练习",
                Mode = "sequential",
                AssemblyType = "collection",
                QuestionLimit = 10,
                TotalScore = 100
            });
        var blueprintJson = await ReadJsonAsync(blueprintResponse);

        Assert.Equal(HttpStatusCode.OK, collectionResponse.StatusCode);
        Assert.Equal("Manual",
            collectionJson.RootElement.GetProperty("item").GetProperty("collectionType").GetString());
        Assert.Equal(HttpStatusCode.OK, replaceResponse.StatusCode);
        Assert.Equal(1, replaceJson.RootElement.GetProperty("questionCount").GetInt32());
        Assert.Equal(HttpStatusCode.OK, blueprintResponse.StatusCode);
        Assert.Equal(collectionId, blueprintJson.RootElement.GetProperty("item").GetProperty("collectionId").GetGuid());

        using var scope = factory.CreateSystemScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Equal(1, dbContext.QuestionCollections.Single(item => item.Id == collectionId).QuestionCount);
        Assert.Contains(dbContext.QuestionCollectionItems, item => item.QuestionId == questionId);
    }

    [Fact]
    public async Task Tenant_admin_can_get_import_field_mapping_and_templates()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedAdminAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        using var mappingResponse =
            await client.GetAsync("/api/tenant/content/imports/field-mapping?importType=questions");
        using var templateResponse =
            await client.GetAsync("/api/tenant/content/imports/templates?importType=questions&format=csv");
        var mapping = await ReadJsonAsync(mappingResponse);
        var template = await ReadJsonAsync(templateResponse);

        Assert.Equal(HttpStatusCode.OK, mappingResponse.StatusCode);
        Assert.Equal("questions", mapping.RootElement.GetProperty("importType").GetString());
        Assert.Contains(mapping.RootElement.GetProperty("requiredFields").EnumerateArray(),
            item => item.GetString() == "type");
        Assert.Equal(HttpStatusCode.OK, templateResponse.StatusCode);
        Assert.Equal("csv", template.RootElement.GetProperty("format").GetString());
        Assert.NotEmpty(template.RootElement.GetProperty("contentBase64").GetString() ?? string.Empty);
    }

    [Fact]
    public async Task ContentEntries_ApplySelfAndRestrictedScopesAndHideUnauthorizedUpdates()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedAdminAsync(factory);
        var allowedRegionId = Guid.NewGuid();
        var outsideRegionId = Guid.NewGuid();
        var regionalCreatorId = Guid.NewGuid();
        var outsideCreatorId = Guid.NewGuid();
        var ownEntry = new ContentEntry
        {
            TenantId = seed.TenantId,
            RegionId = outsideRegionId,
            EntryKey = "own-entry",
            Name = "Own Entry",
            CreatedBy = seed.UserId
        };
        var regionalEntry = new ContentEntry
        {
            TenantId = seed.TenantId,
            RegionId = allowedRegionId,
            EntryKey = "regional-entry",
            Name = "Regional Entry",
            CreatedBy = regionalCreatorId
        };
        var outsideEntry = new ContentEntry
        {
            TenantId = seed.TenantId,
            RegionId = outsideRegionId,
            EntryKey = "outside-entry",
            Name = "Outside Entry",
            CreatedBy = outsideCreatorId
        };
        await factory.SeedAsync(
            new Region { Id = allowedRegionId, TenantId = seed.TenantId, Name = "Allowed Region" },
            new Region { Id = outsideRegionId, TenantId = seed.TenantId, Name = "Outside Region" },
            new User { Id = regionalCreatorId, Name = "Regional Creator" },
            new User { Id = outsideCreatorId, Name = "Outside Creator" },
            ownEntry,
            regionalEntry,
            outsideEntry);
        await SetDataScopeAsync(factory, seed.TenantId, new { mode = "self" });

        using var client = factory.CreateClient();
        await LoginAsync(client, seed);
        using var selfResponse = await client.GetAsync("/api/tenant/content/entries?includeInactive=true");
        using var selfJson = await ReadJsonAsync(selfResponse);

        await SetDataScopeAsync(factory, seed.TenantId, new
        {
            mode = "restricted",
            regionIds = new[] { allowedRegionId },
            includesSelf = false
        });
        using var restrictedResponse = await client.GetAsync("/api/tenant/content/entries?includeInactive=true");
        using var restrictedJson = await ReadJsonAsync(restrictedResponse);
        using var deniedUpdate = await client.PostAsJsonAsync(
            "/api/tenant/content/entries",
            new UpsertContentEntryDto
            {
                Id = outsideEntry.Id,
                RegionId = outsideRegionId,
                EntryKey = outsideEntry.EntryKey,
                Name = "Must stay hidden"
            });

        Assert.Equal([ownEntry.Id], selfJson.RootElement.GetProperty("items").EnumerateArray()
            .Select(item => item.GetProperty("id").GetGuid()));
        Assert.Equal([regionalEntry.Id], restrictedJson.RootElement.GetProperty("items").EnumerateArray()
            .Select(item => item.GetProperty("id").GetGuid()));
        Assert.Equal(HttpStatusCode.NotFound, deniedUpdate.StatusCode);
    }

    private static async Task<Guid> CreateEntryAsync(HttpClient client)
    {
        using var response = await client.PostAsJsonAsync(
            "/api/tenant/content/entries",
            new UpsertContentEntryDto
            {
                EntryKey = Guid.NewGuid().ToString("N"),
                Name = "刷题入口",
                EntryType = "questionPractice"
            });
        var json = await ReadJsonAsync(response);
        return json.RootElement.GetProperty("item").GetProperty("id").GetGuid();
    }

    private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedAdminAsync(ApiTestFactory factory)
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var phone = "13700000000";
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Test Tenant",
                Status = TenantStatus.Active,
                Metadata = JsonDefaults.Object()
            },
            new User
            {
                Id = userId,
                Phone = phone,
                Name = "Tenant Admin"
            }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.TenantAdmin,
                Status = MembershipStatus.Active
            });

        return (tenantId, userId, phone);
    }

    private static async Task LoginAsync(
        HttpClient client,
        (Guid TenantId, Guid UserId, string Phone) seed)
    {
        client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));
    }

    private static async Task SetDataScopeAsync(ApiTestFactory factory, Guid tenantId, object value)
    {
        using var scope = factory.CreateSystemScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var role = dbContext.TenantBackendRoles.Single(item =>
            item.TenantId == tenantId && item.Code == "integration_test_admin");
        role.DataScope = JsonSerializer.SerializeToElement(value);
        await dbContext.SaveChangesAsync();
    }

    private static async Task<JsonDocument> ReadJsonAsync(HttpResponseMessage response)
    {
        var stream = await response.Content.ReadAsStreamAsync();
        return await JsonDocument.ParseAsync(stream);
    }
}