using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

namespace EpsonPrintService;

public sealed class PuduRobotClient
{
    private readonly HttpClient _http;
    private readonly string _baseUrl;
    private readonly string _username;
    private readonly string _password;
    private string? _token;

    public PuduRobotClient(string baseUrl, string username, string password, HttpClient? http = null)
    {
        _baseUrl = baseUrl.TrimEnd('/');
        _username = username;
        _password = password;
        _http = http ?? new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
    }

    public async Task SendToTableAsync(string robotSn, string targetPoint, CancellationToken ct = default)
    {
        await EnsureTokenAsync(ct);
        try
        {
            await PostAddCommandAsync(robotSn, targetPoint, ct);
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            // Token expired — refresh once and retry.
            _token = null;
            await EnsureTokenAsync(ct);
            await PostAddCommandAsync(robotSn, targetPoint, ct);
        }
    }

    private async Task EnsureTokenAsync(CancellationToken ct)
    {
        if (_token is not null) return;

        using var resp = await _http.PostAsJsonAsync(
            $"{_baseUrl}/api/Auth/login",
            new { username = _username, password = _password },
            ct);
        resp.EnsureSuccessStatusCode();

        var bodyText = await resp.Content.ReadAsStringAsync(ct);
        var token = ExtractToken(bodyText);
        if (string.IsNullOrEmpty(token))
            throw new InvalidOperationException($"Pudu login: no token returned. Body: {bodyText}");

        _token = token;
    }

    private async Task PostAddCommandAsync(string robotSn, string targetPoint, CancellationToken ct)
    {
        using var req = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/api/Robots/add-command")
        {
            Content = JsonContent.Create(new { Sn = robotSn, TargetPoint = targetPoint })
        };
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);

        using var resp = await _http.SendAsync(req, ct);
        resp.EnsureSuccessStatusCode();
    }

    // Recursive search so we match the token regardless of wrapping (e.g. {"data":{"token":"..."}}).
    // Mirrors the behavior of `sed -n 's/.*"token":"\([^"]*\)".*/\1/p'` in send_robot.sh.
    private static string? ExtractToken(string json)
    {
        try
        {
            using var doc = JsonDocument.Parse(json);
            return FindTokenProperty(doc.RootElement);
        }
        catch (JsonException)
        {
            return null;
        }
    }

    private static string? FindTokenProperty(JsonElement element)
    {
        switch (element.ValueKind)
        {
            case JsonValueKind.Object:
                foreach (var prop in element.EnumerateObject())
                {
                    if ((prop.Name.Equals("token", StringComparison.OrdinalIgnoreCase)
                         || prop.Name.Equals("accessToken", StringComparison.OrdinalIgnoreCase))
                        && prop.Value.ValueKind == JsonValueKind.String)
                    {
                        return prop.Value.GetString();
                    }
                }
                foreach (var prop in element.EnumerateObject())
                {
                    var nested = FindTokenProperty(prop.Value);
                    if (nested is not null) return nested;
                }
                return null;

            case JsonValueKind.Array:
                foreach (var item in element.EnumerateArray())
                {
                    var nested = FindTokenProperty(item);
                    if (nested is not null) return nested;
                }
                return null;

            default:
                return null;
        }
    }
}
