using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore;
using TrackQrApi.Data;
using TrackQrApi.Models;

namespace TrackQrApi.Features.Events.Services;

public interface IEventTrackingService
{
    Task TrackClickAsync(Guid workspaceId, Guid shortLinkId, HttpContext context);
    Task TrackScanAsync(Guid workspaceId, Guid shortLinkId, Guid qrCodeId, HttpContext context);
}

public class EventTrackingService(
    IServiceScopeFactory scopeFactory,
    IGeoIpService geoIpService,
    ILogger<EventTrackingService> logger)
    : IEventTrackingService
{
    // Dedupe window - same visitor clicking same link within this window counts as one
    private static readonly TimeSpan DedupeWindow = TimeSpan.FromMinutes(30);

    public Task TrackClickAsync(Guid workspaceId, Guid shortLinkId, HttpContext context)
    {
        // Fire and forget - don't block the redirect
        _ = Task.Run(async () =>
        {
            try
            {
                await TrackEventInternalAsync(workspaceId, shortLinkId, null, EventType.Click, context);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Failed to track click event for link {ShortLinkId}", shortLinkId);
            }
        });

        return Task.CompletedTask;
    }

    public Task TrackScanAsync(Guid workspaceId, Guid shortLinkId, Guid qrCodeId, HttpContext context)
    {
        // Fire and forget - don't block the redirect
        _ = Task.Run(async () =>
        {
            try
            {
                await TrackEventInternalAsync(workspaceId, shortLinkId, qrCodeId, EventType.Scan, context);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Failed to track scan event for QR {QRCodeId}", qrCodeId);
            }
        });

        return Task.CompletedTask;
    }

    private async Task TrackEventInternalAsync(
        Guid workspaceId,
        Guid shortLinkId,
        Guid? qrCodeId,
        EventType eventType,
        HttpContext context)
    {
        logger.LogInformation("About to track something");

        // Create a new scope for database access (since we're in a background task)
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var ipAddress = GetClientIpAddress(context);
        var userAgent = context.Request.Headers.UserAgent.ToString();
        var referrer = context.Request.Headers.Referer.ToString();

        var ipHash = HashIpAddress(ipAddress);
        var deviceType = ParseDeviceType(userAgent);
        var countryCode = geoIpService.GetCountryCode(ipAddress);
        var dedupeKey = GenerateDedupeKey(ipHash, shortLinkId, qrCodeId);

        // Check for duplicate within the dedupe window
        var cutoff = DateTime.UtcNow.Subtract(DedupeWindow);
        var isDuplicate = await db.Events
            .AnyAsync(e => e.DedupeKey == dedupeKey && e.Timestamp > cutoff);

        if (isDuplicate)
        {
            logger.LogDebug("Skipping duplicate event for link {ShortLinkId}", shortLinkId);
            return;
        }

        var evt = new Event
        {
            WorkspaceId = workspaceId,
            ShortLinkId = shortLinkId,
            QRCodeId = qrCodeId,
            Type = eventType,
            Timestamp = DateTime.UtcNow,
            IpHash = ipHash,
            UserAgent = TruncateString(userAgent, 512),
            Referrer = TruncateString(referrer, 2048),
            CountryCode = countryCode,
            DeviceType = deviceType,
            DedupeKey = dedupeKey
        };

        db.Events.Add(evt);
        await db.SaveChangesAsync();

        logger.LogDebug("Tracked {EventType} event for link {ShortLinkId}", eventType, shortLinkId);
    }

    private static string GetClientIpAddress(HttpContext context)
    {
        // Check for forwarded headers (when behind a proxy/load balancer)
        var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
        if (!string.IsNullOrEmpty(forwardedFor))
            // Take the first IP in the chain (client IP)
            return forwardedFor.Split(',')[0].Trim();

        return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
    }

    private static string HashIpAddress(string ipAddress)
    {
        // Use SHA256 to hash the IP for privacy
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(ipAddress));
        return Convert.ToHexString(bytes)[..16]; // First 16 chars is enough
    }

    private static string ParseDeviceType(string userAgent)
    {
        if (string.IsNullOrEmpty(userAgent))
            return "Unknown";

        var ua = userAgent.ToLowerInvariant();

        // Check for mobile devices
        if (Regex.IsMatch(ua, @"mobile|android|iphone|ipad|ipod|blackberry|windows phone"))
        {
            if (Regex.IsMatch(ua, @"ipad|tablet|android(?!.*mobile)"))
                return "Tablet";
            return "Mobile";
        }

        // Check for bots/crawlers
        if (Regex.IsMatch(ua, @"bot|crawler|spider|slurp|googlebot|bingbot"))
            return "Bot";

        return "Desktop";
    }

    private static string GenerateDedupeKey(string ipHash, Guid shortLinkId, Guid? qrCodeId)
    {
        // Combine IP hash + link ID + optional QR ID
        var combined = $"{ipHash}:{shortLinkId}:{qrCodeId?.ToString() ?? "none"}";
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(combined));
        return Convert.ToHexString(bytes)[..32];
    }

    private static string? TruncateString(string? value, int maxLength)
    {
        if (string.IsNullOrEmpty(value))
            return null;

        return value.Length <= maxLength ? value : value[..maxLength];
    }
}
