using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static JobTrackerApi.Services.JobApplicationHelpers;

namespace JobTrackerApi.Controllers;

[ApiController]
[Route("api/jobapplications")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class JobApplicationAnalyticsController : ControllerBase
{
    private readonly JobTrackerContext _db;
    private readonly AnalyticsService _analytics;

    public JobApplicationAnalyticsController(JobTrackerContext db, AnalyticsService analytics)
    {
        _db = db;
        _analytics = analytics;
    }

    [HttpGet("stats")]
    public async Task<ActionResult<JobStats>> GetStats(CancellationToken cancellationToken)
        => Ok(await _analytics.GetStatsAsync(cancellationToken));

    [HttpGet("analytics")]
    public async Task<ActionResult<List<AnalyticsPoint>>> GetAnalytics(
        [FromQuery] int months = 12,
        [FromQuery] DateTime? from = null,
        [FromQuery] DateTime? to = null,
        CancellationToken cancellationToken = default)
    {
        months = Math.Clamp(months, 3, 36);
        var now = DateTime.Now;
        DateTime startMonth;
        DateTime endMonth;

        if (from is not null || to is not null)
        {
            var toValue = to ?? now;
            var fromValue = from ?? toValue.AddMonths(-months);
            if (toValue < fromValue) (fromValue, toValue) = (toValue, fromValue);

            startMonth = new DateTime(fromValue.Year, fromValue.Month, 1);
            endMonth = new DateTime(toValue.Year, toValue.Month, 1).AddMonths(1);
            var spanMonths = ((endMonth.Year - startMonth.Year) * 12) + endMonth.Month - startMonth.Month;
            spanMonths = Math.Clamp(spanMonths, 3, 36);
            startMonth = endMonth.AddMonths(-spanMonths);
            months = spanMonths;
        }
        else
        {
            endMonth = new DateTime(now.Year, now.Month, 1).AddMonths(1);
            startMonth = endMonth.AddMonths(-months);
        }

        var jobs = await _db.JobApplications
            .AsNoTracking()
            .Where(job => !job.IsDeleted
                && job.DateApplied != null
                && job.DateApplied >= startMonth
                && job.DateApplied < endMonth)
            .Select(job => new { job.DateApplied, job.ResponseDate })
            .ToListAsync(cancellationToken);

        var applied = new Dictionary<string, int>(StringComparer.Ordinal);
        var responses = new Dictionary<string, int>(StringComparer.Ordinal);
        static string Key(DateTime value) => $"{value:yyyy-MM}";

        foreach (var job in jobs)
        {
            var appliedKey = Key(job.DateApplied!.Value);
            applied[appliedKey] = (applied.TryGetValue(appliedKey, out var appliedCount) ? appliedCount : 0) + 1;
            if (job.ResponseDate is not null)
            {
                var responseKey = Key(job.ResponseDate.Value);
                responses[responseKey] = (responses.TryGetValue(responseKey, out var responseCount) ? responseCount : 0) + 1;
            }
        }

        var result = new List<AnalyticsPoint>(months);
        for (var index = 0; index < months; index++)
        {
            var key = Key(startMonth.AddMonths(index));
            applied.TryGetValue(key, out var appliedCount);
            responses.TryGetValue(key, out var responseCount);
            result.Add(new AnalyticsPoint(key, appliedCount, responseCount));
        }

        return Ok(result);
    }

    [HttpGet("tags")]
    public async Task<ActionResult<List<TagPoint>>> GetTags(
        [FromQuery] int limit = 10,
        [FromQuery] DateTime? from = null,
        [FromQuery] DateTime? to = null,
        CancellationToken cancellationToken = default)
    {
        limit = Math.Clamp(limit, 3, 50);
        IQueryable<JobApplication> query = _db.JobApplications.AsNoTracking().Where(job => !job.IsDeleted);

        if (from is not null || to is not null)
        {
            var toValue = to ?? DateTime.Now;
            var fromValue = from ?? DateTime.MinValue;
            if (toValue < fromValue) (fromValue, toValue) = (toValue, fromValue);

            var startMonth = fromValue == DateTime.MinValue
                ? (DateTime?)null
                : new DateTime(fromValue.Year, fromValue.Month, 1);
            var endMonth = new DateTime(toValue.Year, toValue.Month, 1).AddMonths(1);
            if (startMonth is not null) query = query.Where(job => job.DateApplied >= startMonth.Value);
            query = query.Where(job => job.DateApplied < endMonth);
        }

        var tagStrings = await query.Select(job => job.Tags).ToListAsync(cancellationToken);
        var counts = new Dictionary<string, (string Display, int Count)>(StringComparer.OrdinalIgnoreCase);
        foreach (var tagString in tagStrings)
        {
            foreach (var tag in SplitTags(tagString))
            {
                counts[tag] = counts.TryGetValue(tag, out var value)
                    ? (value.Display, value.Count + 1)
                    : (tag, 1);
            }
        }

        return Ok(counts.Values
            .OrderByDescending(value => value.Count)
            .ThenBy(value => value.Display, StringComparer.OrdinalIgnoreCase)
            .Take(limit)
            .Select(value => new TagPoint(value.Display, value.Count))
            .ToList());
    }

    [HttpGet("analytics-overview")]
    public async Task<ActionResult<AnalyticsOverviewDto>> GetAnalyticsOverview(CancellationToken cancellationToken)
        => Ok(await _analytics.GetAnalyticsOverviewAsync(cancellationToken));

    [HttpGet("tag-trends")]
    public async Task<ActionResult<TagTrendResponse>> GetTagTrends(
        [FromQuery] int months = 6,
        [FromQuery] int limit = 5,
        CancellationToken cancellationToken = default)
    {
        months = Math.Clamp(months, 3, 24);
        limit = Math.Clamp(limit, 3, 10);
        var endMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).AddMonths(1);
        var startMonth = endMonth.AddMonths(-months);
        var jobs = await _db.JobApplications
            .AsNoTracking()
            .Where(job => !job.IsDeleted && job.DateApplied >= startMonth && job.DateApplied < endMonth)
            .Select(job => new { job.DateApplied, job.Tags })
            .ToListAsync(cancellationToken);

        var totals = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        var monthKeys = Enumerable.Range(0, months).Select(index => startMonth.AddMonths(index).ToString("yyyy-MM")).ToList();
        var countsByTag = new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);
        foreach (var job in jobs)
        {
            var monthKey = $"{job.DateApplied:yyyy-MM}";
            foreach (var tag in SplitTags(job.Tags))
            {
                totals[tag] = (totals.TryGetValue(tag, out var total) ? total : 0) + 1;
                if (!countsByTag.TryGetValue(tag, out var countsByMonth))
                {
                    countsByMonth = new Dictionary<string, int>(StringComparer.Ordinal);
                    countsByTag[tag] = countsByMonth;
                }
                countsByMonth[monthKey] = (countsByMonth.TryGetValue(monthKey, out var count) ? count : 0) + 1;
            }
        }

        var topTags = totals
            .OrderByDescending(value => value.Value)
            .ThenBy(value => value.Key, StringComparer.OrdinalIgnoreCase)
            .Take(limit)
            .Select(value => value.Key)
            .ToList();
        var series = topTags
            .Select(tag => new TagTrendSeries(
                tag,
                monthKeys.Select(month => countsByTag.TryGetValue(tag, out var byMonth)
                    && byMonth.TryGetValue(month, out var count) ? count : 0).ToList()))
            .ToList();

        return Ok(new TagTrendResponse(monthKeys, series));
    }
}
