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

namespace Tiku.IntegrationTests.Api;

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

        using var response = await client.GetAsync("/api/learning/favorites/questions");

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

    [Fact]
    public async Task Submit_answer_records_wrong_question_when_self_judged_wrong()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLearningUserAsync(factory);
        var questionId = Guid.NewGuid();
        var sessionQuestionId = await SeedAnswerableQuestionAsync(factory, seed, questionId);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        using var response = await client.PostAsJsonAsync(
            "/api/learning/answers",
            new SubmitAnswerDto
            {
                SessionQuestionId = sessionQuestionId,
                SelectedOptions = ["A"],
                SelfJudgedCorrect = false
            });
        using var wrongResponse = await client.GetAsync("/api/learning/wrong-questions");
        var answer = await ReadJsonAsync(response);
        var wrongItems = await ReadItemsAsync(wrongResponse);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal(sessionQuestionId, answer.RootElement.GetProperty("sessionQuestionId").GetGuid());
        Assert.False(answer.RootElement.GetProperty("isCorrect").GetBoolean());
        var wrong = Assert.Single(wrongItems);
        Assert.Equal(questionId, wrong.GetProperty("locator").GetProperty("questionId").GetGuid());
        Assert.Equal(1, wrong.GetProperty("wrongCount").GetInt32());
    }

    [Fact]
    public async Task Practice_session_can_be_created_detailed_submitted_and_listed()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLearningUserAsync(factory);
        var collectionId = Guid.NewGuid();
        var firstQuestionId = Guid.NewGuid();
        var secondQuestionId = Guid.NewGuid();
        var firstVersionId = Guid.NewGuid();
        var secondVersionId = Guid.NewGuid();
        await factory.SeedQuestionWithVersionAsync(
            new Question
            {
                Id = firstQuestionId,
                TenantId = seed.TenantId,
                Type = "choice",
                Status = QuestionStatus.Published
            },
            new QuestionVersion
            {
                Id = firstVersionId,
                TenantId = seed.TenantId,
                QuestionId = firstQuestionId,
                VersionNo = 1,
                Content = "first"
            });
        await factory.SeedQuestionWithVersionAsync(
            new Question
            {
                Id = secondQuestionId,
                TenantId = seed.TenantId,
                Type = "choice",
                Status = QuestionStatus.Published
            },
            new QuestionVersion
            {
                Id = secondVersionId,
                TenantId = seed.TenantId,
                QuestionId = secondQuestionId,
                VersionNo = 1,
                Content = "second"
            });
        var firstReferenceId = Guid.NewGuid();
        var secondReferenceId = Guid.NewGuid();
        await factory.SeedAsync(
            new TenantQuestionReference
            {
                Id = firstReferenceId,
                TenantId = seed.TenantId,
                QuestionOwnerTenantId = seed.TenantId,
                QuestionId = firstQuestionId,
                Source = QuestionSource.Tenant
            },
            new TenantQuestionReference
            {
                Id = secondReferenceId,
                TenantId = seed.TenantId,
                QuestionOwnerTenantId = seed.TenantId,
                QuestionId = secondQuestionId,
                Source = QuestionSource.Tenant
            },
            new QuestionCollection
            {
                Id = collectionId,
                TenantId = seed.TenantId,
                Name = "基础练习",
                Status = ContentStatus.Active
            },
            new QuestionCollectionItem
            {
                Id = Guid.NewGuid(),
                TenantId = seed.TenantId,
                CollectionId = collectionId,
                QuestionReferenceId = firstReferenceId,
                QuestionOwnerTenantId = seed.TenantId,
                QuestionId = firstQuestionId,
                SortOrder = 1
            },
            new QuestionCollectionItem
            {
                Id = Guid.NewGuid(),
                TenantId = seed.TenantId,
                CollectionId = collectionId,
                QuestionReferenceId = secondReferenceId,
                QuestionOwnerTenantId = seed.TenantId,
                QuestionId = secondQuestionId,
                SortOrder = 2
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var createResponse = await client.PostAsJsonAsync(
            "/api/learning/practice-sessions",
            new CreatePracticeSessionDto
            {
                Mode = "chapter",
                CollectionId = collectionId,
                QuestionLimit = 2,
                TotalScore = 100
            });
        var created = await ReadJsonAsync(createResponse);
        var practiceSessionId = created.RootElement.GetProperty("id").GetGuid();
        var detailResponse = await client.GetAsync($"/api/learning/practice-sessions/detail?practiceSessionId={practiceSessionId}");
        var detail = await ReadJsonAsync(detailResponse);
        var detailQuestions = detail.RootElement.GetProperty("questions").EnumerateArray().ToArray();
        var firstSessionQuestionId = detailQuestions.Single(item =>
            item.GetProperty("questionId").GetGuid() == firstQuestionId).GetProperty("sessionQuestionId").GetGuid();
        var secondSessionQuestionId = detailQuestions.Single(item =>
            item.GetProperty("questionId").GetGuid() == secondQuestionId).GetProperty("sessionQuestionId").GetGuid();

        await client.PostAsJsonAsync(
            "/api/learning/answers",
            new SubmitAnswerDto
            {
                SessionQuestionId = firstSessionQuestionId,
                SelectedOptions = ["A"],
                SelfJudgedCorrect = true
            });
        await client.PostAsJsonAsync(
            "/api/learning/answers",
            new SubmitAnswerDto
            {
                SessionQuestionId = secondSessionQuestionId,
                SelectedOptions = ["B"],
                SelfJudgedCorrect = false
            });
        var submitResponse = await client.PostAsJsonAsync(
            "/api/learning/practice-sessions/submit",
            new SubmitPracticeSessionDto { PracticeSessionId = practiceSessionId });
        var report = await ReadJsonAsync(submitResponse);
        var reportResponse = await client.GetAsync($"/api/learning/practice-sessions/report?practiceSessionId={practiceSessionId}");
        var reportsResponse = await client.GetAsync("/api/learning/practice-reports");
        var historyResponse = await client.GetAsync("/api/learning/practice-sessions/history?status=finished");
        var reports = await ReadItemsAsync(reportsResponse);
        var history = await ReadItemsAsync(historyResponse);

        Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);
        Assert.Equal(2, created.RootElement.GetProperty("questionCount").GetInt32());
        Assert.Equal("active", created.RootElement.GetProperty("status").GetString());
        Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode);
        Assert.Equal(2, detail.RootElement.GetProperty("questions").GetArrayLength());
        Assert.Equal(HttpStatusCode.OK, submitResponse.StatusCode);
        Assert.Equal(2, report.RootElement.GetProperty("totalQuestions").GetInt32());
        Assert.Equal(1, report.RootElement.GetProperty("correctCount").GetInt32());
        Assert.Equal(1, report.RootElement.GetProperty("wrongCount").GetInt32());
        Assert.Equal(50m, report.RootElement.GetProperty("score").GetDecimal());
        Assert.Equal(HttpStatusCode.OK, reportResponse.StatusCode);
        Assert.Single(reports);
        var historyItem = Assert.Single(history);
        Assert.Equal(practiceSessionId, historyItem.GetProperty("id").GetGuid());
        Assert.Equal("finished", historyItem.GetProperty("status").GetString());
    }

    [Fact]
    public async Task Favorite_question_can_be_added_listed_and_removed()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLearningUserAsync(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 addResponse = await client.PostAsJsonAsync(
            "/api/learning/favorites/questions",
            new QuestionActionDto { QuestionId = questionId });
        var listResponse = await client.GetAsync("/api/learning/favorites/questions");
        var itemsAfterAdd = await ReadItemsAsync(listResponse);
        var removeResponse = await client.PostAsJsonAsync(
            "/api/learning/favorites/questions",
            new QuestionActionDto { QuestionId = questionId, Favorite = false });
        var emptyResponse = await client.GetAsync("/api/learning/favorites/questions");
        var itemsAfterRemove = await ReadItemsAsync(emptyResponse);

        Assert.Equal(HttpStatusCode.OK, addResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
        Assert.Equal(
            questionId,
            Assert.Single(itemsAfterAdd).GetProperty("locator").GetProperty("questionId").GetGuid());
        Assert.Equal(HttpStatusCode.OK, removeResponse.StatusCode);
        Assert.Empty(itemsAfterRemove);
    }

    [Fact]
    public async Task Word_progress_upserts_and_filters_by_status()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLearningUserAsync(factory);
        var wordId = Guid.NewGuid();
        await factory.SeedAsync(new VocabularyWord
        {
            Id = wordId,
            TenantId = seed.TenantId,
            Word = "composition",
            IsActive = true
        });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        using var updateResponse = await client.PostAsJsonAsync(
            "/api/learning/vocabulary/progress",
            new WordProgressDto
            {
                WordId = wordId,
                Status = "learning",
                CorrectDelta = 2
            });
        using var listResponse = await client.GetAsync("/api/learning/vocabulary/progress?status=learning");
        var progress = await ReadJsonAsync(updateResponse);
        var items = await ReadItemsAsync(listResponse);

        Assert.Equal(HttpStatusCode.OK, updateResponse.StatusCode);
        Assert.Equal(2, progress.RootElement.GetProperty("correctCount").GetInt32());
        var item = Assert.Single(items);
        Assert.Equal(wordId, item.GetProperty("wordId").GetGuid());
        Assert.Equal("Learning", item.GetProperty("status").GetString());
    }

    [Fact]
    public async Task Favorite_words_can_be_filtered_by_unit()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLearningUserAsync(factory);
        var unitId = Guid.NewGuid();
        var otherUnitId = Guid.NewGuid();
        var wordId = Guid.NewGuid();
        var otherWordId = Guid.NewGuid();
        await factory.SeedAsync(
            new VocabularyUnit
            {
                Id = unitId,
                TenantId = seed.TenantId,
                Name = "Unit 1",
                IsActive = true
            },
            new VocabularyUnit
            {
                Id = otherUnitId,
                TenantId = seed.TenantId,
                Name = "Unit 2",
                IsActive = true
            },
            new VocabularyWord
            {
                Id = wordId,
                TenantId = seed.TenantId,
                UnitId = unitId,
                Word = "design",
                IsActive = true
            },
            new VocabularyWord
            {
                Id = otherWordId,
                TenantId = seed.TenantId,
                UnitId = otherUnitId,
                Word = "sketch",
                IsActive = true
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        await client.PostAsJsonAsync(
            "/api/learning/vocabulary/favorites",
            new FavoriteWordDto { WordId = wordId, Note = "重点" });
        await client.PostAsJsonAsync(
            "/api/learning/vocabulary/favorites",
            new FavoriteWordDto { WordId = otherWordId });
        using var response = await client.GetAsync($"/api/learning/vocabulary/favorites?unitId={unitId}");
        var items = await ReadItemsAsync(response);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var item = Assert.Single(items);
        Assert.Equal(wordId, item.GetProperty("wordId").GetGuid());
        Assert.Equal("重点", item.GetProperty("note").GetString());
    }

    [Fact]
    public async Task Learning_stats_plans_leaderboard_and_word_review_are_available()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedLearningUserAsync(factory);
        var questionId = Guid.NewGuid();
        var wordId = Guid.NewGuid();
        var sessionQuestionId = await SeedAnswerableQuestionAsync(factory, seed, questionId);
        await factory.SeedAsync(
            new VocabularyWord
            {
                Id = wordId,
                TenantId = seed.TenantId,
                Word = "review",
                IsActive = true
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        await client.PostAsJsonAsync(
            "/api/learning/answers",
            new SubmitAnswerDto
            {
                SessionQuestionId = sessionQuestionId,
                SelectedOptions = ["A"],
                SelfJudgedCorrect = false
            });
        await client.PostAsJsonAsync(
            "/api/learning/vocabulary/progress",
            new WordProgressDto
            {
                WordId = wordId,
                Status = "learning",
                WrongDelta = 1,
                NextReviewAt = DateTimeOffset.UtcNow.AddMinutes(-1)
            });

        var statsResponse = await client.GetAsync("/api/learning/stats");
        var trendResponse = await client.GetAsync("/api/learning/trend?limit=7");
        var leaderboardResponse = await client.GetAsync("/api/learning/leaderboard?limit=10");
        var wrongPlanResponse = await client.GetAsync("/api/learning/wrong-questions/review-plan");
        var wordPlanResponse = await client.GetAsync("/api/learning/vocabulary/review-plan");
        var reviewResponse = await client.PostAsJsonAsync(
            "/api/learning/vocabulary/review",
            new WordReviewDto { WordId = wordId, Result = "correct" });
        var wordStatsResponse = await client.GetAsync("/api/learning/vocabulary/stats");

        var stats = await ReadJsonAsync(statsResponse);
        var trend = await ReadItemsAsync(trendResponse);
        var leaderboard = await ReadJsonAsync(leaderboardResponse);
        var wrongPlan = await ReadJsonAsync(wrongPlanResponse);
        var wordPlan = await ReadJsonAsync(wordPlanResponse);
        var review = await ReadJsonAsync(reviewResponse);
        var wordStats = await ReadJsonAsync(wordStatsResponse);

        Assert.Equal(HttpStatusCode.OK, statsResponse.StatusCode);
        Assert.Equal(1, stats.RootElement.GetProperty("answerCount").GetInt32());
        Assert.Equal(1, stats.RootElement.GetProperty("wrongCount").GetInt32());
        Assert.NotEmpty(trend);
        Assert.Equal(seed.UserId, Assert.Single(leaderboard.RootElement.GetProperty("items").EnumerateArray()).GetProperty("userId").GetGuid());
        Assert.Single(wrongPlan.RootElement.GetProperty("items").EnumerateArray());
        Assert.Single(wordPlan.RootElement.GetProperty("items").EnumerateArray());
        Assert.Equal(1, review.RootElement.GetProperty("correctCount").GetInt32());
        Assert.Equal(1, wordStats.RootElement.GetProperty("total").GetInt32());
    }

    private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedLearningUserAsync(
        ApiTestFactory factory)
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var phone = "13900000000";
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Learning Tenant"
            },
            new User
            {
                Id = userId,
                Phone = phone,
                Name = "Learning User"
            }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.Student,
                Status = MembershipStatus.Active
            });

        return (tenantId, userId, phone);
    }

    private static async Task<Guid> SeedAnswerableQuestionAsync(
        ApiTestFactory factory,
        (Guid TenantId, Guid UserId, string Phone) seed,
        Guid questionId)
    {
        var versionId = Guid.NewGuid();
        await factory.SeedQuestionWithVersionAsync(
            new Question
            {
                Id = questionId,
                TenantId = seed.TenantId,
                Type = "choice",
                Status = QuestionStatus.Published
            },
            new QuestionVersion
            {
                Id = versionId,
                TenantId = seed.TenantId,
                QuestionId = questionId,
                VersionNo = 1,
                Content = "answerable question"
            });

        var referenceId = Guid.NewGuid();
        var sessionId = Guid.NewGuid();
        var sessionQuestionId = Guid.NewGuid();
        await factory.SeedAsync(
            new TenantQuestionReference
            {
                Id = referenceId,
                TenantId = seed.TenantId,
                QuestionOwnerTenantId = seed.TenantId,
                QuestionId = questionId,
                Source = QuestionSource.Tenant
            },
            new PracticeSession
            {
                Id = sessionId,
                TenantId = seed.TenantId,
                UserId = seed.UserId,
                Mode = "single",
                QuestionCount = 1,
                ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(30)
            },
            new PracticeSessionQuestion
            {
                Id = sessionQuestionId,
                TenantId = seed.TenantId,
                PracticeSessionId = sessionId,
                QuestionReferenceId = referenceId,
                QuestionOwnerTenantId = seed.TenantId,
                QuestionId = questionId,
                QuestionVersionId = versionId,
                Position = 0
            });
        return sessionQuestionId;
    }

    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<JsonDocument> ReadJsonAsync(HttpResponseMessage response)
    {
        var stream = await response.Content.ReadAsStreamAsync();
        return await JsonDocument.ParseAsync(stream);
    }

    private static async Task<JsonElement[]> ReadItemsAsync(HttpResponseMessage response)
    {
        var body = await ReadJsonAsync(response);
        return body.RootElement
            .GetProperty("items")
            .EnumerateArray()
            .Select(item => item.Clone())
            .ToArray();
    }

}
