using System.Text.Json;
using BrassAndSigil.Server.Models;

namespace BrassAndSigil.Server.Services;

/// <summary>
/// Tracks "I want to play" requests from friends, pending admin approval.
/// State is a flat JSON file in the server dir so it survives daemon restarts
/// without needing a database. Single-flight gate prevents concurrent-write
/// corruption when admin and friend act at the same time.
///
/// State machine:  (none) -> pending -> approved | denied
/// "approved" means the admin clicked Approve; the actual /whitelist add
/// command goes through the existing whitelist endpoint, which removes the
/// request from the pending list.
/// </summary>
public sealed class WhitelistRequestService
{
    private readonly ServerConfig _config;
    private readonly object _lock = new();

    public WhitelistRequestService(ServerConfig config) => _config = config;

    public sealed class Request
    {
        public string Username  { get; set; } = "";
        public string? Message  { get; set; }
        public string Status    { get; set; } = "pending"; // pending | approved | denied
        public DateTimeOffset RequestedAt { get; set; }
        public DateTimeOffset? ResolvedAt { get; set; }
        public string? RemoteIp { get; set; } // for admin diagnosis if needed
    }

    private string FilePath =>
        Path.Combine(Path.GetFullPath(_config.ServerDir), "whitelist-requests.json");

    private List<Request> Load()
    {
        try
        {
            if (!File.Exists(FilePath)) return new();
            var text = File.ReadAllText(FilePath);
            if (string.IsNullOrWhiteSpace(text)) return new();
            return JsonSerializer.Deserialize<List<Request>>(text, JsonOpts.CaseInsensitive) ?? new();
        }
        catch { return new(); }
    }

    private void Save(List<Request> list)
    {
        var text = JsonSerializer.Serialize(list, JsonOpts.Pretty);
        File.WriteAllText(FilePath, text);
    }

    public IReadOnlyList<Request> List() { lock (_lock) return Load(); }

    public IReadOnlyList<Request> ListPending()
    {
        lock (_lock)
            return Load().Where(r => r.Status == "pending").ToList();
    }

    /// <summary>Submit a new request. Idempotent on (username, status=pending) -- won't dupe.</summary>
    public Request Submit(string username, string? message, string? remoteIp)
    {
        lock (_lock)
        {
            var list = Load();
            // Drop any prior request for this username (case-insensitive) so the
            // most recent one wins regardless of previous state. Keeps the file
            // from growing if a friend re-requests after a denial.
            list.RemoveAll(r => r.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
            var req = new Request
            {
                Username    = username,
                Message     = string.IsNullOrWhiteSpace(message) ? null : message,
                Status      = "pending",
                RequestedAt = DateTimeOffset.UtcNow,
                RemoteIp    = remoteIp,
            };
            list.Add(req);
            Save(list);
            return req;
        }
    }

    /// <summary>
    /// Effective status for the launcher. If the username is in the actual
    /// whitelist.json (regardless of whether they ever filed a request), returns
    /// "approved" -- that's what the friend's launcher cares about. Otherwise
    /// falls back to whatever request record we have, or "unknown".
    /// </summary>
    public string StatusFor(string username)
    {
        if (IsActuallyWhitelisted(username)) return "approved";
        lock (_lock)
        {
            var match = Load().FirstOrDefault(r => r.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
            return match?.Status ?? "unknown";
        }
    }

    private bool IsActuallyWhitelisted(string username)
    {
        var path = Path.Combine(Path.GetFullPath(_config.ServerDir), "whitelist.json");
        if (!File.Exists(path)) return false;
        try
        {
            using var doc = JsonDocument.Parse(File.ReadAllText(path));
            return doc.RootElement.EnumerateArray().Any(e =>
                e.TryGetProperty("name", out var n) &&
                string.Equals(n.GetString(), username, StringComparison.OrdinalIgnoreCase));
        }
        catch { return false; }
    }

    public bool MarkApproved(string username) => SetStatus(username, "approved");
    public bool MarkDenied(string username)   => SetStatus(username, "denied");

    /// <summary>Remove the request entirely (used after the actual /whitelist add fires).</summary>
    public bool Remove(string username)
    {
        lock (_lock)
        {
            var list = Load();
            var removed = list.RemoveAll(r => r.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
            if (removed > 0) Save(list);
            return removed > 0;
        }
    }

    private bool SetStatus(string username, string status)
    {
        lock (_lock)
        {
            var list = Load();
            var match = list.FirstOrDefault(r => r.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
            if (match is null) return false;
            match.Status = status;
            match.ResolvedAt = DateTimeOffset.UtcNow;
            Save(list);
            return true;
        }
    }
}
