using System.Net;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker;
using ClaudeDo.Worker.Config;
using ClaudeDo.Worker.Tests.Infrastructure;
using ClaudeDo.Worker.Tickets;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Tests.Tickets;

public sealed class TicketImportServiceTests : IDisposable
{
    private const string ListId = "l1";
    private readonly List<DbFixture> _dbs = new();
    private readonly List<string> _patDirs = new();

    public void Dispose()
    {
        foreach (var db in _dbs) db.Dispose();
        foreach (var dir in _patDirs)
        {
            try { Directory.Delete(dir, recursive: true); } catch { /* best effort */ }
        }
    }

    [Fact]
    public async Task Imports_only_open_tickets_assigned_to_the_token_owner()
    {
        var (import, ctx) = await HarnessAsync();
        await SeedExistingTaskAsync(ctx, listId: ListId, ticketRef: "bandel:4");

        var result = await import.ImportAsync(ListId, CancellationToken.None);

        Assert.Equal(4, result.Examined);
        Assert.Equal(1, result.Created);

        var created = await ctx.Tasks.Where(t => t.TicketRef == "bandel:1").SingleAsync();
        Assert.Equal("Fix X", created.Title);
        Assert.Equal(TaskStatus.Idle, created.Status);
        Assert.Equal("ticketsystem", created.CreatedBy);
        Assert.Contains("Ticket #1", created.Description);
    }

    [Fact]
    public async Task Assignee_match_is_case_insensitive()
    {
        // Board liefert "Mika.Kuns", /pat/me liefert "mika.kuns"
        var (import, _) = await HarnessAsync(boardAssignee: "Mika.Kuns", identityUser: "mika.kuns");

        var result = await import.ImportAsync(ListId, CancellationToken.None);

        Assert.Equal(1, result.Created);
    }

    [Fact]
    public async Task Running_twice_creates_no_duplicates()
    {
        var (import, ctx) = await HarnessAsync();

        var first = await import.ImportAsync(ListId, CancellationToken.None);
        var second = await import.ImportAsync(ListId, CancellationToken.None);

        Assert.Equal(1, first.Created);
        Assert.Equal(0, second.Created);
        Assert.Equal(1, await ctx.Tasks.CountAsync(t => t.TicketRef == "bandel:1"));
    }

    [Fact]
    public async Task Throws_when_the_list_has_no_ticket_project()
    {
        var (import, _) = await HarnessAsync(ticketProjectId: null);

        await Assert.ThrowsAsync<TicketApiException>(() => import.ImportAsync(ListId, CancellationToken.None));
    }

    [Fact]
    public async Task Throws_when_not_configured()
    {
        var (import, _) = await HarnessAsync(configured: false);

        await Assert.ThrowsAsync<TicketApiException>(() => import.ImportAsync(ListId, CancellationToken.None));
    }

    [Fact]
    public async Task Imported_tasks_get_a_task_number()
    {
        var (import, ctx) = await HarnessAsync();

        await import.ImportAsync(ListId, CancellationToken.None);

        var created = await ctx.Tasks.Where(t => t.TicketRef == "bandel:1").SingleAsync();
        Assert.True(created.Number > 0);
    }

    // ── Helfer ──────────────────────────────────────────────────────────────

    private sealed class StubHttpClientFactory : IHttpClientFactory
    {
        private readonly StubHandler _handler;
        public StubHttpClientFactory(StubHandler handler) => _handler = handler;
        public HttpClient CreateClient(string name) => new(_handler);
    }

    /// <summary>
    /// Board liefert immer vier Tickets: #1 offen/mein (importierbar), #2 offen/fremd (Assignee-
    /// Filter), #3 in Bearbeitung/mein (Status-Filter), #4 offen/mein aber bereits importiert
    /// (Dedup-Filter — hier vorab als bestehender Task angelegt, damit alle Default-Aufrufe
    /// dieselbe, in sich konsistente Ausgangslage haben; Test 1 legt ihn zusätzlich explizit an,
    /// was denselben Zustand nur redundant reproduziert).
    /// </summary>
    private async Task<(TicketImportService Import, ClaudeDoDbContext Ctx)> HarnessAsync(
        bool configured = true,
        int? ticketProjectId = 393,
        string boardAssignee = "mika.kuns",
        string identityUser = "mika.kuns")
    {
        var db = new DbFixture();
        _dbs.Add(db);
        var factory = db.CreateFactory();

        await using (var seed = factory.CreateDbContext())
        {
            seed.Lists.Add(new ListEntity
            {
                Id = ListId,
                Name = "L",
                CreatedAt = DateTime.UtcNow,
                DefaultCommitType = "chore",
            });
            seed.ListConfigs.Add(new ListConfigEntity { ListId = ListId, TicketProjectId = ticketProjectId });
            await seed.SaveChangesAsync();
        }

        await using (var seed = factory.CreateDbContext())
        {
            await SeedExistingTaskAsync(seed, ListId, "bandel:4");
        }

        var handler = new StubHandler(request =>
        {
            if (request.RequestUri!.AbsolutePath.EndsWith("/pat/me", StringComparison.Ordinal))
            {
                return (HttpStatusCode.OK, $$"""
                    {"success":true,"data":{"userName":"{{identityUser}}","isPat":true,"scopes":["pat:board:read"]},"message":"ok"}
                    """);
            }

            return (HttpStatusCode.OK, $$"""
                {"success":true,"data":[
                    {"id":1,"title":"Fix X","description":"d","statusId":1,"statusName":"Offen","assigneeID":7,"assigneeName":"{{boardAssignee}}"},
                    {"id":2,"title":"Other","description":null,"statusId":1,"statusName":"Offen","assigneeID":8,"assigneeName":"jemand.anders"},
                    {"id":3,"title":"InProgress","description":null,"statusId":2,"statusName":"InBearbeitung","assigneeID":7,"assigneeName":"{{boardAssignee}}"},
                    {"id":4,"title":"AlreadyImported","description":null,"statusId":1,"statusName":"Offen","assigneeID":7,"assigneeName":"{{boardAssignee}}"}
                ],"message":"ok"}
                """);
        });

        var patDir = Path.Combine(Path.GetTempPath(), "cdo-ticketimport-" + Guid.NewGuid().ToString("N"));
        _patDirs.Add(patDir);
        var patStore = new TicketPatStore(new DpapiTokenStore(Path.Combine(patDir, "ticket.pat")));
        var worker = new WorkerConfig();
        if (configured)
        {
            worker.TicketApiBaseUrl = "http://api.local";
            patStore.Store.Save("tsp_x");
        }

        var config = new TicketSystemConfig(worker, patStore);
        var clients = new TicketClientFactory(config, new StubHttpClientFactory(handler), NullLogger<BandelTicketClient>.Instance);
        var import = new TicketImportService(clients, factory, NullLogger<TicketImportService>.Instance);

        return (import, factory.CreateDbContext());
    }

    private static async Task SeedExistingTaskAsync(ClaudeDoDbContext ctx, string listId, string ticketRef)
    {
        var repo = new TaskRepository(ctx);
        await repo.AddAsync(new TaskEntity
        {
            Id = Guid.NewGuid().ToString(),
            ListId = listId,
            Title = "Pre-existing",
            Status = TaskStatus.Idle,
            CreatedAt = DateTime.UtcNow,
            TicketRef = ticketRef,
        });
    }
}
