using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Shared.Constants;

namespace PowderCoating.Web.Controllers;

/// <summary>
/// SuperAdmin-only viewer for the application audit log, which records who changed
/// what entity and when.  Audit log records are never soft-deleted or modified by
/// the application — they are append-only by design to maintain an unambiguous
/// trail of changes across all tenants.
/// </summary>
// Intentional exception: platform audit log with a long PK; append-only infrastructure table outside the business entity graph; same reasoning as SystemLogsController. See docs/DATA_ACCESS_ARCHITECTURE.md — Permanent Exceptions.
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class AuditLogController : Controller
{
    private readonly ApplicationDbContext _db;
    private readonly ILogger<AuditLogController> _logger;

    private readonly IPlatformSettingsService _platformSettings;

    public AuditLogController(ApplicationDbContext db, ILogger<AuditLogController> logger,
        IPlatformSettingsService platformSettings)
    {
        _db = db;
        _logger = logger;
        _platformSettings = platformSettings;
    }

    /// <summary>
    /// Renders a paginated, filterable audit log across all companies.  Supports
    /// free-text search on username, entity description, and entity Id; and
    /// structured filters for entity type, action verb, company, and date range.
    /// <para>
    /// Date filters are converted to UTC before querying because audit log timestamps
    /// are stored in UTC.  The <c>to</c> date filter adds one day so that entries
    /// timestamped at any point during the selected end-date are included.
    /// Entity-type and action dropdowns are populated from distinct values in the
    /// table (not from a static enum) so the UI always reflects exactly what types
    /// and actions are present in the log.
    /// </para>
    /// </summary>
    public async Task<IActionResult> Index(
        string? search,
        string? entityType,
        [FromQuery] string? action,
        int? companyId,
        DateTime? from,
        DateTime? to,
        int page = 1,
        int pageSize = 50)
    {
        pageSize = pageSize is 25 or 50 or 100 ? pageSize : 50;
        page = Math.Max(1, page);

        var query = _db.AuditLogs.AsNoTracking();

        if (!string.IsNullOrWhiteSpace(search))
            query = query.Where(a =>
                a.UserName.Contains(search) ||
                (a.EntityDescription != null && a.EntityDescription.Contains(search)) ||
                (a.EntityId != null && a.EntityId.Contains(search)));

        if (!string.IsNullOrWhiteSpace(entityType))
            query = query.Where(a => a.EntityType == entityType);

        if (!string.IsNullOrWhiteSpace(action))
            query = query.Where(a => a.Action == action);

        if (companyId.HasValue)
            query = query.Where(a => a.CompanyId == companyId);

        if (from.HasValue)
            query = query.Where(a => a.Timestamp >= from.Value.ToUniversalTime());

        if (to.HasValue)
            query = query.Where(a => a.Timestamp < to.Value.ToUniversalTime().AddDays(1));

        var totalCount = await query.CountAsync();
        var logs = await query
            .OrderByDescending(a => a.Timestamp)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        // Populate filter dropdowns from distinct values
        ViewBag.EntityTypes = await _db.AuditLogs.AsNoTracking()
            .Select(a => a.EntityType).Distinct().OrderBy(x => x).ToListAsync();
        ViewBag.Companies = await _db.Companies.AsNoTracking().IgnoreQueryFilters()
            .Where(c => !c.IsDeleted).OrderBy(c => c.CompanyName)
            .Select(c => new { c.Id, c.CompanyName }).ToListAsync();

        ViewBag.Search = search;
        ViewBag.EntityType = entityType;
        ViewBag.Action = action;
        ViewBag.CompanyId = companyId;
        ViewBag.From = from?.ToString("yyyy-MM-dd");
        ViewBag.To = to?.ToString("yyyy-MM-dd");
        ViewBag.Page = page;
        ViewBag.PageSize = pageSize;
        ViewBag.TotalCount = totalCount;
        ViewBag.TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize);

        return View(logs);
    }

    /// <summary>
    /// Returns the full detail view for a single audit log entry, including the
    /// before/after JSON snapshot stored in the record.  Uses <c>long</c> for the
    /// Id parameter because audit log primary keys are 64-bit integers to
    /// accommodate high-volume writes without risk of overflow.
    /// </summary>
    public async Task<IActionResult> Details(long id)
    {
        var log = await _db.AuditLogs.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
        if (log == null) return NotFound();
        return View(log);
    }

    /// <summary>
    /// Diagnostic action — tests a live INSERT into AuditLogs and reports table state.
    /// Useful for diagnosing why prod shows 0 entries: isolates whether the table is
    /// writable, what the retention setting is, and whether any rows exist at all.
    /// Returns JSON so it can be called from the browser address bar without a view.
    /// </summary>
    [HttpGet]
    public async Task<IActionResult> Diagnostics()
    {
        var results = new Dictionary<string, object?>();

        // 1. Row count
        try
        {
            results["rowCount"] = await _db.AuditLogs.AsNoTracking().CountAsync();
        }
        catch (Exception ex)
        {
            results["rowCountError"] = ex.Message;
        }

        // 2. Oldest and newest timestamps
        try
        {
            var oldest = await _db.AuditLogs.AsNoTracking().MinAsync(a => (DateTime?)a.Timestamp);
            var newest = await _db.AuditLogs.AsNoTracking().MaxAsync(a => (DateTime?)a.Timestamp);
            results["oldestEntry"] = oldest;
            results["newestEntry"] = newest;
        }
        catch (Exception ex)
        {
            results["timestampError"] = ex.Message;
        }

        // 3. Retention setting
        try
        {
            var raw = await _platformSettings.GetAsync(PlatformSettingKeys.AuditLogRetentionDays);
            results["retentionRaw"] = raw;
            results["retentionDays"] = int.TryParse(raw, out var d) ? d : 365;
            results["retentionCutoff"] = DateTime.UtcNow.AddDays(-(int.TryParse(raw, out var d2) ? d2 : 365));
        }
        catch (Exception ex)
        {
            results["retentionError"] = ex.Message;
        }

        // 4. Test direct INSERT via EF (same path used by WriteLoginAuditAsync)
        string? insertError = null;
        long? insertedId = null;
        try
        {
            var testEntry = new AuditLog
            {
                UserId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value,
                UserName = User.Identity?.Name ?? "Diagnostics",
                Action = "DiagnosticsTest",
                EntityType = "AuditLog",
                EntityId = "diag",
                EntityDescription = "Diagnostic test write",
                IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),
                Timestamp = DateTime.UtcNow
            };
            _db.AuditLogs.Add(testEntry);
            await _db.SaveChangesAsync();
            insertedId = testEntry.Id;
            results["testInsertId"] = insertedId;
            results["testInsertSuccess"] = true;

            // Clean it up immediately
            _db.AuditLogs.Remove(testEntry);
            await _db.SaveChangesAsync();
            results["testInsertCleanedUp"] = true;
        }
        catch (Exception ex)
        {
            insertError = ex.Message;
            results["testInsertSuccess"] = false;
            results["testInsertError"] = ex.Message;
            results["testInsertInner"] = ex.InnerException?.Message;
        }

        // 5. Connection string info (safe — shows server/db only, no credentials)
        try
        {
            var conn = _db.Database.GetConnectionString() ?? "";
            // Extract Server= and Database= segments only (no passwords)
            var safeConn = string.Join("; ", conn.Split(';')
                .Where(p => p.TrimStart().StartsWith("Server", StringComparison.OrdinalIgnoreCase)
                         || p.TrimStart().StartsWith("Database", StringComparison.OrdinalIgnoreCase)
                         || p.TrimStart().StartsWith("Data Source", StringComparison.OrdinalIgnoreCase)
                         || p.TrimStart().StartsWith("Initial Catalog", StringComparison.OrdinalIgnoreCase)));
            results["connectionInfo"] = safeConn;
        }
        catch (Exception ex)
        {
            results["connectionError"] = ex.Message;
        }

        results["serverUtcNow"] = DateTime.UtcNow;
        results["environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "unknown";

        _logger.LogInformation("Audit log diagnostics run by {User}: {@Results}", User.Identity?.Name, results);

        return Json(results, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
    }
}
