using System.Data;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.Interfaces;

namespace PowderCoating.Infrastructure.Data;

/// <summary>
/// Writes manual audit log entries via raw SQL, matching the approach used by
/// <see cref="AuditInterceptor"/> so neither path can trigger an infinite loop
/// through the EF change tracker.
/// </summary>
public class AuditService : IAuditService
{
    private readonly ApplicationDbContext _db;
    private readonly IHttpContextAccessor _http;

    public AuditService(ApplicationDbContext db, IHttpContextAccessor http)
    {
        _db = db;
        _http = http;
    }

    /// <inheritdoc/>
    public async Task LogAsync(string action, string entityType, string? description = null,
                               object? details = null, string? entityId = null)
    {
        var (userId, userName, companyId, ip) = GetRequestContext();
        var newValues = details != null ? JsonSerializer.Serialize(details) : null;

        const string sql = """
            INSERT INTO AuditLogs
                (UserId, UserName, CompanyId, CompanyName, Action, EntityType, EntityId,
                 EntityDescription, OldValues, NewValues, IpAddress, Timestamp)
            VALUES (@userId, @userName, @companyId, @companyName, @action, @entityType, @entityId,
                    @entityDescription, NULL, @newValues, @ipAddress, @timestamp)
            """;

        SqlParameter[] p =
        [
            new("@userId",            SqlDbType.NVarChar, -1) { Value = (object?)userId       ?? DBNull.Value },
            new("@userName",          SqlDbType.NVarChar, -1) { Value = (object)userName },
            new("@companyId",         SqlDbType.Int)          { Value = (object?)companyId     ?? DBNull.Value },
            new("@companyName",       SqlDbType.NVarChar, -1) { Value = DBNull.Value },
            new("@action",            SqlDbType.NVarChar, -1) { Value = (object)action },
            new("@entityType",        SqlDbType.NVarChar, -1) { Value = (object)entityType },
            new("@entityId",          SqlDbType.NVarChar, -1) { Value = (object?)entityId      ?? DBNull.Value },
            new("@entityDescription", SqlDbType.NVarChar, -1) { Value = (object?)description   ?? DBNull.Value },
            new("@newValues",         SqlDbType.NVarChar, -1) { Value = (object?)newValues      ?? DBNull.Value },
            new("@ipAddress",         SqlDbType.NVarChar, -1) { Value = (object?)ip             ?? DBNull.Value },
            new("@timestamp",         SqlDbType.DateTime2)    { Value = DateTime.UtcNow },
        ];

        await _db.Database.ExecuteSqlRawAsync(sql, p);
    }

    private (string? userId, string userName, int? companyId, string? ip) GetRequestContext()
    {
        var ctx = _http.HttpContext;
        if (ctx == null) return (null, "System", null, null);

        var user      = ctx.User;
        var userId    = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
        var userName  = user.Identity?.Name
                     ?? user.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value
                     ?? "Unknown";
        var cidStr    = user.FindFirst("CompanyId")?.Value;
        int? companyId = cidStr != null && int.TryParse(cidStr, out var c) ? c : null;
        var ip        = ctx.Connection.RemoteIpAddress?.ToString();

        return (userId, userName, companyId, ip);
    }
}
