using System.Net.Http.Headers;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace JobTrackerApi.Controllers;

[ApiController]
[Route("api/job-discovery")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class JobDiscoveryController : ControllerBase
{
    private const string BaseUrl = "https://pam-stilling-feed.nav.no";
    private readonly IHttpClientFactory _clients;
    private readonly IConfiguration _configuration;
    private readonly IMemoryCache _cache;

    public JobDiscoveryController(IHttpClientFactory clients, IConfiguration configuration, IMemoryCache cache)
    {
        _clients = clients;
        _configuration = configuration;
        _cache = cache;
    }

    [HttpGet("search")]
    public async Task<ActionResult<IReadOnlyList<DiscoveredJob>>> Search([FromQuery] string? q, [FromQuery] string? location, CancellationToken cancellationToken)
    {
        try
        {
            var retrievedAt = DateTimeOffset.UtcNow;
            var token = await GetTokenAsync(cancellationToken);
            var client = _clients.CreateClient();
            var entries = new Dictionary<string, DiscoveredJob>(StringComparer.OrdinalIgnoreCase);
            var next = "/api/v1/feed";

            // ponytail: scan the recent event window on demand; add a persisted feed cursor only when
            // usage makes the bounded request noticeably slow or NAV private-token terms require it.
            for (var page = 0; page < 20 && !string.IsNullOrWhiteSpace(next); page++)
            {
                using var request = new HttpRequestMessage(HttpMethod.Get, BaseUrl + next);
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                if (page == 0) request.Headers.IfModifiedSince = DateTimeOffset.UtcNow.AddDays(-14);

                using var response = await client.SendAsync(request, cancellationToken);
                response.EnsureSuccessStatusCode();
                using var json = JsonDocument.Parse(await response.Content.ReadAsStreamAsync(cancellationToken));
                next = json.RootElement.TryGetProperty("next_url", out var nextElement) ? nextElement.GetString() ?? "" : "";

                foreach (var item in json.RootElement.GetProperty("items").EnumerateArray())
                {
                    var feed = item.GetProperty("_feed_entry");
                    var id = feed.GetProperty("uuid").GetString();
                    if (string.IsNullOrWhiteSpace(id)) continue;
                    var status = feed.TryGetProperty("status", out var statusElement) ? statusElement.GetString() : null;
                    if (!string.Equals(status, "ACTIVE", StringComparison.OrdinalIgnoreCase))
                    {
                        entries.Remove(id);
                        continue;
                    }

                    entries[id] = new DiscoveredJob(
                        id,
                        feed.TryGetProperty("title", out var title) ? title.GetString() ?? "" : "",
                        feed.TryGetProperty("businessName", out var company) ? company.GetString() : null,
                        feed.TryGetProperty("municipal", out var municipal) ? municipal.GetString() : null,
                        item.TryGetProperty("date_modified", out var modified) && modified.TryGetDateTimeOffset(out var date) ? date : null,
                        feed.TryGetProperty("applicationDue", out var applicationDue) && applicationDue.TryGetDateTimeOffset(out var deadline) ? deadline : null,
                        $"https://arbeidsplassen.nav.no/stillinger/stilling/{id}",
                        "nav",
                        "NAV Arbeidsplassen",
                        "searched",
                        retrievedAt,
                        "NO");
                }
            }

            var query = (q ?? "").Trim();
            var place = (location ?? "").Trim();
            return Ok(entries.Values
                .Where(job => Contains(job.Title, query) || Contains(job.Company, query))
                .Where(job => Contains(job.Location, place))
                .OrderByDescending(job => job.ModifiedAt)
                .Take(100)
                .ToList());
        }
        catch (Exception ex) when (ex is HttpRequestException or JsonException or InvalidOperationException)
        {
            return Problem("NAV job discovery is temporarily unavailable.", statusCode: StatusCodes.Status502BadGateway);
        }
    }

    private async Task<string> GetTokenAsync(CancellationToken cancellationToken)
    {
        var configured = _configuration["NavJobs:Token"]?.Trim();
        if (!string.IsNullOrWhiteSpace(configured)) return configured;

        return await _cache.GetOrCreateAsync("nav-jobs-public-token", async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
            var text = await _clients.CreateClient().GetStringAsync(BaseUrl + "/api/publicToken", cancellationToken);
            var start = text.IndexOf("eyJ", StringComparison.Ordinal);
            if (start < 0) throw new InvalidOperationException("NAV public token was not returned.");
            var token = text[start..].Trim();
            var end = token.IndexOfAny(['\r', '\n', ' ', '\t']);
            return end < 0 ? token : token[..end];
        }) ?? throw new InvalidOperationException("NAV public token was not returned.");
    }

    private static bool Contains(string? value, string filter) =>
        filter.Length == 0 || (value?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false);

    public sealed record DiscoveredJob(
        string Id,
        string Title,
        string? Company,
        string? Location,
        DateTimeOffset? ModifiedAt,
        DateTimeOffset? Deadline,
        string Url,
        string Source,
        string SourceName,
        string AcquisitionType,
        DateTimeOffset RetrievedAt,
        string CountryCode);
}
