using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Contracts;
using Tiku.Application.Profile;
using Tiku.Domain.Catalog;
using Tiku.Domain.Commerce;
using Tiku.Domain.Common;
using Tiku.Domain.Identity;
using Tiku.Domain.Learning;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class ProfileEndpointTests
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        Converters = { new JsonStringEnumConverter() }
    };

    [Fact]
    public async Task Logged_in_student_can_get_and_update_profile()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedStudentAsync(factory);
        var regionId = Guid.NewGuid();
        var schoolId = Guid.NewGuid();
        var majorId = Guid.NewGuid();
        await factory.SeedAsync(
            new Region { Id = regionId, TenantId = seed.TenantId, Name = "四川" },
            new School { Id = schoolId, TenantId = seed.TenantId, RegionId = regionId, Name = "美术学院" },
            new Major
            {
                Id = majorId, TenantId = seed.TenantId, RegionId = regionId, SchoolId = schoolId, Name = "视觉传达"
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        using var getResponse = await client.GetAsync("/api/student/profile/me");
        var getJson = await ReadJsonAsync(getResponse);
        using var patchResponse = await client.PatchAsJsonAsync(
            "/api/student/profile/me",
            new UpdateProfileDto
            {
                Name = "张三",
                AvatarPreset = "female",
                RegionId = regionId,
                SelectedSchoolId = schoolId,
                SelectedMajorId = majorId,
                Stats = JsonSerializer.SerializeToElement(new { level = 3 }),
                RecentActivities = JsonSerializer.SerializeToElement(new[] { new { type = "login" } })
            });
        var patchJson = await ReadJsonAsync(patchResponse);

        Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
        Assert.NotEqual(Guid.Empty, getJson.RootElement.GetProperty("profileId").GetGuid());
        Assert.Equal(HttpStatusCode.OK, patchResponse.StatusCode);
        Assert.Equal("张三", patchJson.RootElement.GetProperty("name").GetString());
        Assert.Equal("female", patchJson.RootElement.GetProperty("avatarPreset").GetString());
        Assert.Equal(regionId, patchJson.RootElement.GetProperty("regionId").GetGuid());
    }

    [Fact]
    public async Task Exam_countdowns_use_current_profile_target()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedStudentAsync(factory);
        var regionId = Guid.NewGuid();
        var schoolId = Guid.NewGuid();
        var otherRegionId = Guid.NewGuid();
        await factory.SeedAsync(
            new Region { Id = regionId, TenantId = seed.TenantId, Name = "四川" },
            new Region { Id = otherRegionId, TenantId = seed.TenantId, Name = "其他地区" },
            new School { Id = schoolId, TenantId = seed.TenantId, RegionId = regionId, Name = "美术学院" },
            new StudentProfile
            {
                TenantId = seed.TenantId,
                UserId = seed.UserId,
                RegionId = regionId,
                SelectedSchoolId = schoolId
            },
            new ExamDate
            {
                TenantId = seed.TenantId,
                RegionId = regionId,
                SchoolId = schoolId,
                ExamName = "校考",
                ExamAt = DateTimeOffset.UtcNow.AddDays(10)
            },
            new ExamDate
            {
                TenantId = seed.TenantId,
                RegionId = otherRegionId,
                ExamName = "其他地区考试",
                ExamAt = DateTimeOffset.UtcNow.AddDays(1)
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        using var response = await client.GetAsync("/api/student/profile/exam-countdowns");
        var json = await ReadJsonAsync(response);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var item = json.RootElement.GetProperty("items").EnumerateArray().Single();
        Assert.Equal("校考", item.GetProperty("examName").GetString());
        Assert.True(item.GetProperty("daysLeft").GetInt32() >= 9);
    }

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

        using var response = await client.GetAsync("/api/student/profile/me");

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

    [Fact]
    public async Task Student_can_manage_notifications_badges_and_feedbacks()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedStudentAsync(factory);
        var notificationId = Guid.NewGuid();
        var badgeId = Guid.NewGuid();
        await factory.SeedAsync(
            new StudentProfile
            {
                TenantId = seed.TenantId,
                UserId = seed.UserId
            },
            new UserNotification
            {
                Id = notificationId,
                TenantId = seed.TenantId,
                UserId = seed.UserId,
                NotificationType = "system",
                Title = "欢迎",
                Message = "欢迎回来",
                Status = NotificationStatus.Unread
            },
            new Badge
            {
                Id = badgeId,
                TenantId = seed.TenantId,
                Name = "首练",
                Category = "learning",
                IsActive = true
            },
            new UserBadge
            {
                TenantId = seed.TenantId,
                UserId = seed.UserId,
                BadgeId = badgeId,
                GrantedAt = DateTimeOffset.UtcNow
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var notificationsResponse = await client.GetAsync("/api/student/profile/notifications");
        var notificationsJson = await ReadJsonAsync(notificationsResponse);
        var statusResponse = await client.PostAsJsonAsync(
            "/api/student/profile/notifications/status",
            new NotificationStatusDto { NotificationIds = [notificationId], Status = "read" });
        var badgesResponse = await client.GetAsync("/api/student/profile/badges?includeLocked=true");
        var badgesJson = await ReadJsonAsync(badgesResponse);
        var feedbackResponse = await client.PostAsJsonAsync(
            "/api/student/profile/feedbacks",
            new SubmitFeedbackDto
            {
                Type = "suggestion",
                Title = "建议",
                Description = "希望增加练习提醒",
                Priority = "normal"
            });
        var feedbackJson = await ReadJsonAsync(feedbackResponse);
        var feedbacksResponse = await client.GetAsync("/api/student/profile/feedbacks?type=suggestion");
        var feedbacksJson = await ReadJsonAsync(feedbacksResponse);

        Assert.Equal(HttpStatusCode.OK, notificationsResponse.StatusCode);
        Assert.Equal("unread",
            notificationsJson.RootElement.GetProperty("items").EnumerateArray().Single().GetProperty("status")
                .GetString());
        Assert.Equal(HttpStatusCode.OK, statusResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, badgesResponse.StatusCode);
        Assert.True(badgesJson.RootElement.GetProperty("items").EnumerateArray().Single().GetProperty("isUnlocked")
            .GetBoolean());
        Assert.Equal(HttpStatusCode.OK, feedbackResponse.StatusCode);
        Assert.Equal("suggestion", feedbackJson.RootElement.GetProperty("type").GetString());
        Assert.Equal(HttpStatusCode.OK, feedbacksResponse.StatusCode);
        Assert.Single(feedbacksJson.RootElement.EnumerateArray());

        using var scope = factory.CreateSystemScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Equal(NotificationStatus.Read,
            dbContext.UserNotifications.Single(item => item.Id == notificationId).Status);
        Assert.Single(dbContext.ReportStatusEvents);
    }

    [Fact]
    public async Task Student_can_check_in_once_per_day_and_query_score_events()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedStudentAsync(factory);
        await factory.SeedAsync(
            new StudentProfile
            {
                TenantId = seed.TenantId,
                UserId = seed.UserId
            },
            new PointActivityTask
            {
                TenantId = seed.TenantId,
                TaskKey = "daily_check_in",
                Title = "每日签到",
                TaskType = PointActivityTaskType.DailyLogin,
                Points = 5,
                MaxClaimsPerUser = 3650
            });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var firstResponse = await client.PostAsync("/api/student/profile/check-in", null);
        var first = await firstResponse.Content.ReadFromJsonAsync<CheckInResult>(JsonOptions);
        var secondResponse = await client.PostAsync("/api/student/profile/check-in", null);
        var second = await secondResponse.Content.ReadFromJsonAsync<CheckInResult>(JsonOptions);
        var eventsResponse = await client.GetAsync("/api/student/profile/score-events?sourceType=daily_check_in");
        var events = await eventsResponse.Content.ReadFromJsonAsync<ProfileScoreEventList>(JsonOptions);

        Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
        Assert.False(first!.AlreadyCheckedIn);
        Assert.Equal(5, first.Points);
        Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
        Assert.True(second!.AlreadyCheckedIn);
        Assert.Equal(first.ScoreEventId, second.ScoreEventId);
        Assert.Equal(HttpStatusCode.OK, eventsResponse.StatusCode);
        var item = Assert.Single(events!.Items);
        Assert.Equal("check_in", item.EventType);
        Assert.Equal(5, item.Points);

        using var scope = factory.CreateSystemScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Single(dbContext.PointActivityClaims.Where(claim =>
            claim.TenantId == seed.TenantId &&
            claim.UserId == seed.UserId &&
            claim.SourceType == "daily_check_in"));
        Assert.Single(dbContext.UserScoreEvents.Where(scoreEvent =>
            scoreEvent.TenantId == seed.TenantId &&
            scoreEvent.UserId == seed.UserId &&
            scoreEvent.SourceType == "daily_check_in"));
    }

    private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedStudentAsync(ApiTestFactory factory)
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var phone = $"136{Random.Shared.Next(10000000, 99999999)}";
        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 = "Student"
            }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.Student,
                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<JsonDocument> ReadJsonAsync(HttpResponseMessage response)
    {
        var stream = await response.Content.ReadAsStreamAsync();
        return await JsonDocument.ParseAsync(stream);
    }
}