using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Online.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Online;

public sealed class OnlineSyncService : BackgroundService
{
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly IOnlineInboxApi _api;
    private readonly IOnlineAuthProvider _auth;
    private readonly OnlineInboxConfig _config;
    private readonly ILogger<OnlineSyncService> _logger;
    private readonly HubBroadcaster _broadcaster;

    public OnlineSyncService(
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        IOnlineInboxApi api,
        IOnlineAuthProvider auth,
        OnlineInboxConfig config,
        ILogger<OnlineSyncService> logger,
        HubBroadcaster broadcaster)
    {
        _dbFactory = dbFactory;
        _api = api;
        _auth = auth;
        _config = config;
        _logger = logger;
        _broadcaster = broadcaster;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await TickAsync(stoppingToken);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                return;
            }
            catch (OnlineInboxException ex) when (ex.StatusCode == 401)
            {
                _logger.LogError(
                    "OnlineSyncService: {Message} Sync is paused until you sign in again with an authorized account.",
                    ex.Message);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "OnlineSyncService cycle failed; backing off to next interval");
            }

            try
            {
                await Task.Delay(TimeSpan.FromSeconds(_config.PollIntervalSeconds), stoppingToken);
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }
    }

    internal async Task TickAsync(CancellationToken ct)
    {
        // Respect a runtime disable. The hosted service stays registered for the process
        // lifetime, so toggling the feature off in Settings must stop all sync + auth work
        // (incl. OIDC discovery) here — otherwise the loop keeps polling until a restart.
        if (!_config.Enabled)
            return;

        var token = await _auth.GetAccessTokenAsync(ct);
        if (token is null)
        {
            _logger.LogDebug("OnlineSyncService: no access token, skipping cycle");
            return;
        }

        // Resolve the current user's Zitadel subject so sync payloads carry an owner and pulls
        // can be guarded. Null today (single user / server derives it from the token).
        var ownerId = JwtClaims.GetSubject(token);

        await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
        var tasks = new TaskRepository(ctx);
        var lists = new ListRepository(ctx);

        // Step 1: pull unimported tasks, import them locally, mark each imported.
        var unimported = await _api.GetUnimportedTasksAsync(ct);
        foreach (var remote in unimported)
        {
            // Multi-user guard: never import a task explicitly owned by a different user.
            // Unowned tasks (ownerId == null) stay importable so single-user behavior is intact.
            if (ownerId is not null && remote.OwnerId is not null && remote.OwnerId != ownerId)
            {
                _logger.LogWarning(
                    "OnlineSyncService: remote task {Id} is owned by another user; skipping", remote.Id);
                continue;
            }

            var existing = await tasks.GetByIdAsync(remote.Id, ct);
            if (existing is not null)
            {
                // Already imported locally; just mark it on the server.
                await _api.MarkImportedAsync(remote.Id, ct);
                continue;
            }

            var list = await lists.GetByIdAsync(remote.ListId, ct);
            if (list is null)
            {
                _logger.LogWarning(
                    "OnlineSyncService: remote task {Id} references unknown list {ListId}; skipping",
                    remote.Id, remote.ListId);
                continue;
            }

            var entity = new TaskEntity
            {
                Id = remote.Id,
                ListId = remote.ListId,
                Title = remote.Title,
                Description = remote.Description,
                Status = TaskStatus.Idle,
                CreatedBy = "online",
                CreatedAt = remote.CreatedAt.UtcDateTime,
                CommitType = CommitTypeRegistry.DefaultType,
            };
            await tasks.AddAsync(entity, ct);
            // Without this the imported task only shows up after a manual reload.
            await _broadcaster.TaskUpdated(entity.Id);
            await _api.MarkImportedAsync(remote.Id, ct);

            _logger.LogInformation("OnlineSyncService: imported task {Id} ('{Title}')", remote.Id, remote.Title);
        }

        // Step 2: push full list catalog, stamped with the owner.
        var allLists = await lists.GetAllAsync(ct);
        var remoteLists = allLists.Select(l => new RemoteList(l.Id, l.Name, ownerId)).ToList();
        await _api.PutListsAsync(remoteLists, ct);

        // Step 3: push current Idle backlog mirror, stamped with the owner.
        var mirror = (await OnlineBacklog.CurrentAsync(tasks, ct))
            .Select(m => m with { OwnerId = ownerId })
            .ToList();
        await _api.PutMirrorAsync(mirror, ct);
    }
}
