using System.Globalization;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace JobTrackerApi.Controllers;

[ApiController]
[Route("api/admin/audit")]
[Authorize(Roles = "Admin")]
public sealed class AdminAuditController : ControllerBase
{
    private readonly JobTrackerContext _db;

    public AdminAuditController(JobTrackerContext db)
    {
        _db = db;
    }

    public sealed record AuditItemDto(
        int Id,
        string Type,
        string? OldValue,
        string? NewValue,
        string? Note,
        DateTime At,
        int JobApplicationId,
        string? JobTitle,
        string? CompanyName,
        string? OwnerUserId,
        string? OwnerEmail,
        string? OwnerUserName
    );

    [HttpGet]
    public async Task<ActionResult<List<AuditItemDto>>> Get([FromQuery] int take = 200, CancellationToken cancellationToken = default)
    {
        if (take < 1) take = 1;
        if (take > 2000) take = 2000;

        var items = await _db.JobEvents
            .IgnoreQueryFilters()
            .AsNoTracking()
            .OrderByDescending(e => e.At)
            .Take(take)
            .Select(e => new
            {
                e.Id,
                e.Type,
                e.OldValue,
                e.NewValue,
                e.Note,
                e.At,
                e.JobApplicationId
            })
            .ToListAsync(cancellationToken);

        var jobIds = items.Select(x => x.JobApplicationId).Distinct().ToList();

        var jobs = await _db.JobApplications
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Include(j => j.Company)
            .Where(j => jobIds.Contains(j.Id))
            .Select(j => new { j.Id, j.JobTitle, CompanyName = j.Company.Name, j.OwnerUserId })
            .ToDictionaryAsync(x => x.Id, cancellationToken);

        var ownerIds = jobs.Values
            .Select(x => x.OwnerUserId)
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Distinct()
            .ToList();

        var owners = ownerIds.Count == 0
            ? new Dictionary<string, (string? Email, string? UserName)>()
            : await _db.Users
                .AsNoTracking()
                .Where(u => ownerIds.Contains(u.Id))
                .Select(u => new { u.Id, u.Email, u.UserName })
                .ToDictionaryAsync(x => x.Id, x => (x.Email, x.UserName), cancellationToken);

        var outList = new List<AuditItemDto>(items.Count);
        foreach (var e in items)
        {
            jobs.TryGetValue(e.JobApplicationId, out var j);

            (string? Email, string? UserName) owner = default;
            if (j?.OwnerUserId is not null)
            {
                owners.TryGetValue(j.OwnerUserId, out owner);
            }

            outList.Add(new AuditItemDto(
                e.Id,
                e.Type,
                e.OldValue,
                e.NewValue,
                e.Note,
                e.At,
                e.JobApplicationId,
                j?.JobTitle,
                j?.CompanyName,
                j?.OwnerUserId,
                owner.Email,
                owner.UserName
            ));
        }

        return Ok(outList);
    }

    public sealed record UndoResultDto(bool Ok, string Message);

    [HttpPost("{id:int}/undo")]
    public async Task<ActionResult<UndoResultDto>> Undo([FromRoute] int id, CancellationToken cancellationToken = default)
    {
        var ev = await _db.JobEvents
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(e => e.Id == id, cancellationToken);

        if (ev is null) return NotFound(new UndoResultDto(false, "Event not found."));

        var job = await _db.JobApplications
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(j => j.Id == ev.JobApplicationId, cancellationToken);

        if (job is null) return NotFound(new UndoResultDto(false, "Job application not found."));

        string message;
        string? undoOld = ev.NewValue;
        string? undoNew = ev.OldValue;

        switch ((ev.Type ?? "").Trim())
        {
            case "StatusChanged":
            {
                if (string.IsNullOrWhiteSpace(ev.OldValue))
                    return BadRequest(new UndoResultDto(false, "Cannot undo: missing OldValue."));

                var prev = job.Status;
                job.Status = ev.OldValue.Trim();
                message = $"Reverted status '{prev}' -> '{job.Status}'.";
                break;
            }
            case "FollowUpSet":
            {
                job.FollowUpAt = ParseNullableIso(ev.OldValue);
                message = "Reverted follow-up date.";
                break;
            }
            case "ResponseUpdated":
            {
                if (!TryParseResponse(ev.OldValue, out var received, out var date))
                    return BadRequest(new UndoResultDto(false, "Cannot undo: invalid OldValue."));

                job.ResponseReceived = received;
                job.ResponseDate = date;
                message = "Reverted response fields.";
                break;
            }
            case "Deleted":
            {
                job.IsDeleted = false;
                job.DeletedAt = null;
                message = "Restored job from trash.";
                break;
            }
            case "Restored":
            {
                job.IsDeleted = true;
                job.DeletedAt = DateTime.Now;
                message = "Moved job back to trash.";
                break;
            }
            default:
                return BadRequest(new UndoResultDto(false, $"Undo is not supported for event type '{ev.Type}'."));
        }

        _db.JobEvents.Add(new JobEvent
        {
            JobApplicationId = job.Id,
            Type = "Undo",
            OldValue = undoOld,
            NewValue = undoNew,
            Note = $"Undid event #{ev.Id} ({ev.Type}). {message}",
            At = DateTime.Now
        });

        await _db.SaveChangesAsync(cancellationToken);
        return Ok(new UndoResultDto(true, message));
    }

    private static DateTime? ParseNullableIso(string? iso)
    {
        if (string.IsNullOrWhiteSpace(iso)) return null;
        if (DateTime.TryParse(iso, null, DateTimeStyles.RoundtripKind, out var d)) return d;
        if (DateTime.TryParse(iso, out d)) return d;
        return null;
    }

    private static bool TryParseResponse(string? value, out bool received, out DateTime? date)
    {
        received = false;
        date = null;
        if (string.IsNullOrWhiteSpace(value)) return false;

        var idx = value.IndexOf(':');
        if (idx < 0) return false;

        var b = value[..idx];
        var rest = value[(idx + 1)..];

        if (!bool.TryParse(b, out received)) return false;
        date = ParseNullableIso(rest);
        return true;
    }
}
