using System.Net;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using UniVerse.Application.DTOs.Reviews;
using UniVerse.Application.Interfaces;
using UniVerse.Application.Prompts;
using UniVerse.Domain.Exceptions;
using UniVerse.Infrastructure.Data;
using UniVerse.Infrastructure.ExternalServices;
using UniVerse.Infrastructure.Services;
using Xunit;

namespace UniVerse.Api.Tests.Reviews;

public class ReviewPromptServiceTests
{
    [Fact]
    public async Task GetAsync_ReturnsDefaultPrompt_WhenSettingDoesNotExist()
    {
        await using var db = CreateDbContext();
        var service = new ReviewPromptService(db);

        var result = await service.GetAsync();

        Assert.Equal(ReviewPromptTemplate.Default, result.Prompt);
        Assert.Null(result.UpdatedAt);
    }

    [Fact]
    public async Task UpdateAsync_UpsertsSingletonPrompt()
    {
        await using var db = CreateDbContext();
        var service = new ReviewPromptService(db);

        await service.UpdateAsync(new UpdateReviewPromptRequest("First {lectureContext} {reviewText}"));
        var result = await service.UpdateAsync(new UpdateReviewPromptRequest("Second {lectureContext} {reviewText}"));

        Assert.Equal("Second {lectureContext} {reviewText}", result.Prompt);
        Assert.NotNull(result.UpdatedAt);
        Assert.Equal(1, await db.ReviewPromptSettings.CountAsync());
        Assert.Equal("Second {lectureContext} {reviewText}", (await service.GetAsync()).Prompt);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("Prompt without placeholders")]
    [InlineData("Only lecture {lectureContext}")]
    [InlineData("Only review {reviewText}")]
    public async Task UpdateAsync_RejectsInvalidPrompt(string prompt)
    {
        await using var db = CreateDbContext();
        var service = new ReviewPromptService(db);

        await Assert.ThrowsAsync<BadRequestException>(() =>
            service.UpdateAsync(new UpdateReviewPromptRequest(prompt)));
    }

    [Fact]
    public async Task AnalyzeReviewAsync_RendersCustomPrompt()
    {
        var handler = new CapturingHandler();
        var http = new HttpClient(handler)
        {
            BaseAddress = new Uri("https://llm.test/")
        };
        var config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Llm:Model"] = "test-model",
                ["Llm:ApiKey"] = "test-key"
            })
            .Build();
        var promptService = Substitute.For<IReviewPromptService>();
        promptService.GetAsync().Returns(new ReviewPromptDto(
            "Custom prompt. Context: {lectureContext}. Text: {reviewText}",
            DateTime.UtcNow));
        var client = new LlmClient(http, config, promptService, NullLogger<LlmClient>.Instance);

        await client.AnalyzeReviewAsync("Very useful review", "Lecture: Algebra");

        Assert.NotNull(handler.RequestBody);
        using var requestJson = JsonDocument.Parse(handler.RequestBody!);
        var content = requestJson.RootElement
            .GetProperty("messages")[0]
            .GetProperty("content")
            .GetString();

        Assert.Contains("Custom prompt", content);
        Assert.Contains("Lecture: Algebra", content);
        Assert.Contains("Very useful review", content);
        Assert.DoesNotContain(ReviewPromptTemplate.LectureContextPlaceholder, content);
        Assert.DoesNotContain(ReviewPromptTemplate.ReviewTextPlaceholder, content);
    }

    [Fact]
    public async Task AnalyzeReviewAsync_ParsesSnakeCaseJsonFromFencedResponse()
    {
        var handler = new CapturingHandler("""
            ```json
            {"quality_score":0.82,"sentiment":"Положительный","tags":["lecture structure","practical examples"],"is_informative":true}
            ```
            """);
        var http = new HttpClient(handler)
        {
            BaseAddress = new Uri("https://llm.test/")
        };
        var config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Llm:Model"] = "test-model",
                ["Llm:ApiKey"] = "test-key"
            })
            .Build();
        var promptService = Substitute.For<IReviewPromptService>();
        promptService.GetAsync().Returns(new ReviewPromptDto(ReviewPromptTemplate.Default, null));
        var client = new LlmClient(http, config, promptService, NullLogger<LlmClient>.Instance);

        var result = await client.AnalyzeReviewAsync("Very useful review", "Lecture: Algebra");

        Assert.Equal(0.82, result.QualityScore);
        Assert.Equal("Положительный", result.Sentiment);
        Assert.Equal(["lecture structure", "practical examples"], result.Tags);
        Assert.True(result.IsInformative);
        Assert.Equal("""
            ```json
            {"quality_score":0.82,"sentiment":"Положительный","tags":["lecture structure","practical examples"],"is_informative":true}
            ```
            """, result.RawOutput);
    }

    private static AppDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase($"ReviewPromptServiceTests_{Guid.NewGuid()}")
            .Options;
        return new AppDbContext(options);
    }

    private sealed class CapturingHandler : HttpMessageHandler
    {
        private readonly string _analysisContent;

        public CapturingHandler(string? analysisContent = null)
        {
            _analysisContent = analysisContent ??
                "{\"quality_score\":0.8,\"sentiment\":\"Positive\",\"tags\":[\"practice\"],\"is_informative\":true}";
        }

        public string? RequestBody { get; private set; }

        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            RequestBody = request.Content is null
                ? null
                : await request.Content.ReadAsStringAsync(cancellationToken);

            var responsePayload = JsonSerializer.Serialize(new
            {
                choices = new[]
                {
                    new
                    {
                        message = new
                        {
                            content = _analysisContent
                        }
                    }
                }
            });

            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(responsePayload, Encoding.UTF8, "application/json")
            };
        }
    }
}
