using System.Data;
using Dapper;

namespace Books.Api.EventFlow.Infrastructure;

/// <summary>
/// Configures Dapper type handlers for PostgreSQL/Npgsql compatibility.
/// Npgsql 6.0+ returns DateOnly for DATE columns by default.
/// </summary>
public static class DapperConfiguration
{
    private static bool _initialized;

    public static void Configure()
    {
        if (_initialized) return;

        // Handle DateOnly serialization/deserialization for Dapper
        SqlMapper.AddTypeHandler(new DateOnlyDapperHandler());
        SqlMapper.AddTypeHandler(new NullableDateOnlyDapperHandler());

        // Handle DateOnly -> DateTime conversion for reading DATE columns
        SqlMapper.AddTypeHandler(new DateOnlyTypeHandler());

        // Handle DateTime -> DateTimeOffset? conversion safely (treats DateTime.MinValue as null)
        SqlMapper.AddTypeHandler(new NullableDateTimeOffsetTypeHandler());

        _initialized = true;
    }
}

/// <summary>
/// Dapper type handler for DateOnly - allows DateOnly to be used as a parameter value.
/// </summary>
public class DateOnlyDapperHandler : SqlMapper.TypeHandler<DateOnly>
{
    public override void SetValue(IDbDataParameter parameter, DateOnly value)
    {
        parameter.Value = value.ToDateTime(TimeOnly.MinValue);
        parameter.DbType = DbType.Date;
    }

    public override DateOnly Parse(object value)
    {
        return value switch
        {
            DateOnly dateOnly => dateOnly,
            DateTime dateTime => DateOnly.FromDateTime(dateTime),
            _ => throw new InvalidCastException($"Cannot convert {value?.GetType().Name ?? "null"} to DateOnly")
        };
    }
}

/// <summary>
/// Dapper type handler for nullable DateOnly - allows DateOnly? to be used as a parameter value.
/// </summary>
public class NullableDateOnlyDapperHandler : SqlMapper.TypeHandler<DateOnly?>
{
    public override void SetValue(IDbDataParameter parameter, DateOnly? value)
    {
        if (value.HasValue)
        {
            parameter.Value = value.Value.ToDateTime(TimeOnly.MinValue);
            parameter.DbType = DbType.Date;
        }
        else
        {
            parameter.Value = DBNull.Value;
            parameter.DbType = DbType.Date;
        }
    }

    public override DateOnly? Parse(object value)
    {
        if (value == null || value == DBNull.Value)
            return null;

        return value switch
        {
            DateOnly dateOnly => dateOnly,
            DateTime dateTime => DateOnly.FromDateTime(dateTime),
            _ => throw new InvalidCastException($"Cannot convert {value?.GetType().Name ?? "null"} to DateOnly?")
        };
    }
}

/// <summary>
/// Dapper type handler to convert PostgreSQL DATE (DateOnly) to DateTime.
/// </summary>
public class DateOnlyTypeHandler : SqlMapper.TypeHandler<DateTime>
{
    public override void SetValue(IDbDataParameter parameter, DateTime value)
    {
        parameter.Value = DateOnly.FromDateTime(value);
        parameter.DbType = DbType.Date;
    }

    public override DateTime Parse(object value)
    {
        return value switch
        {
            DateOnly dateOnly => dateOnly.ToDateTime(TimeOnly.MinValue),
            DateTime dateTime => dateTime,
            _ => throw new InvalidCastException($"Cannot convert {value?.GetType().Name ?? "null"} to DateTime")
        };
    }
}

/// <summary>
/// Dapper type handler to safely convert DateTime to nullable DateTimeOffset.
/// Treats DateTime.MinValue (0001-01-01) as null since DateTimeOffset cannot represent it.
/// </summary>
public class NullableDateTimeOffsetTypeHandler : SqlMapper.TypeHandler<DateTimeOffset?>
{
    public override void SetValue(IDbDataParameter parameter, DateTimeOffset? value)
    {
        parameter.Value = value.HasValue ? value.Value.UtcDateTime : DBNull.Value;
        parameter.DbType = DbType.DateTime;
    }

    public override DateTimeOffset? Parse(object value)
    {
        if (value == null || value == DBNull.Value)
            return null;

        if (value is DateTime dateTime)
        {
            // Treat DateTime.MinValue as null (cannot convert to DateTimeOffset)
            if (dateTime.Year <= 1)
                return null;

            return new DateTimeOffset(DateTime.SpecifyKind(dateTime, DateTimeKind.Utc));
        }

        if (value is DateTimeOffset dto)
            return dto;

        return null;
    }
}
