using Dapper;
using System.Data;
using Yuna.Website.Server.Model;

namespace Yuna.Website.Server.Storage.Repositories.Device
{
    public class DeviceRepository : IDeviceRepository
    {
        private readonly DapperContext _context;

        public DeviceRepository(DapperContext context)
        {
            _context = context;
        }

        public async Task AddProps(IReadOnlyList<Model.Prop> props, long deviceId)
        {
            var query =
                $@"INSERT INTO ""Yuna_Props_In_Devices""
                (""PropId"", ""DeviceId"")
                VALUES
                {string.Join(", ", props.Select(prop => $"({prop.Id}, {deviceId})"))}";

            await _context.Connection.ExecuteAsync(query);
        }

        public async Task<Model.Device?> Create(Model.Device device)
        {
            var query =
                $@"INSERT INTO ""Yuna_Devices""
                (""Name"", ""Description"", ""DeviceUrl"", ""UserId"")
                VALUES
                ('{device.Name}', '{device.Description}', '{device.DeviceUrl}', {device.UserId})
                RETURNING *";

            var result = await _context.Connection.QuerySingleAsync<Model.Device?>(query, device);
            return result;
        }

        public async Task<Model.Device?> Delete(long id)
        {
            var query =
                $@"
                UPDATE ""Yuna_Devices""
                SET ""IsDeleted"" = TRUE
                WHERE ""Id"" = {id}
                AND NOT ""IsDeleted""
                returning *
            ";

            return await _context.Connection.QuerySingleOrDefaultAsync<Model.Device>(query);
        }

        public async Task<Model.Device?> GetById(long id)
        {
            var query =
                $@"SELECT 
            d.""Id"" as {nameof(Model.Device.Id)}, 
            d.""Name"" as {nameof(Model.Device.Name)}, 
            d.""Description"" as {nameof(Model.Device.Description)},
            d.""DeviceUrl"" as {nameof(Model.Device.DeviceUrl)},
            d.""UserId"" as {nameof(Model.Device.UserId)},
            p.""Id"" as {nameof(Model.Prop.Id)}, 
            p.""Name"" as {nameof(Model.Prop.Name)},
            p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)},
            p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
            p.""Type"" as {nameof(Model.Prop.Type)}
            FROM ""Yuna_Devices"" d
            LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId""
            LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id""
            WHERE d.""Id"" = {id}
            AND NOT d.""IsDeleted""";

            var deviceDict = new Dictionary<long, Model.Device>();
            var result = await _context.Connection.QueryAsync<Model.Device, Model.Prop, Model.Device>(
                query,
                (device, prop) =>
                {
                    if (!deviceDict.TryGetValue(device.Id, out var currentDevice))
                    {
                        currentDevice = device;
                        currentDevice.Props = new List<Model.Prop>();
                        deviceDict.Add(currentDevice.Id, currentDevice);
                    }
                    currentDevice.Props.Add(prop);
                    return currentDevice;
                },
                splitOn: $"{nameof(Model.Prop.Id)}" // Specify the column to split the results on
            );

            return result.FirstOrDefault();
        }

        public async Task<IReadOnlyList<Model.Device>> GetList()
        {
            var query =
                $@"SELECT 
                    d.""Id"" as {nameof(Model.Device.Id)}, 
                    d.""Name"" as {nameof(Model.Device.Name)}, 
                    d.""Description"" as {nameof(Model.Device.Description)},
                    d.""DeviceUrl"" as {nameof(Model.Device.DeviceUrl)},
                    d.""UserId"" as {nameof(Model.Device.UserId)},
                    p.""Id"" as {nameof(Model.Prop.Id)}, 
                    p.""Name"" as {nameof(Model.Prop.Name)},
                    p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)},
                    p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
                    p.""Type"" as {nameof(Model.Prop.Type)}
                    FROM ""Yuna_Devices"" d
                    LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId""
                    LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id""
                    WHERE NOT d.""IsDeleted""
                    ORDER BY d.""Id""";

            var deviceDict = new Dictionary<long, Model.Device>();
            var result = await _context.Connection.QueryAsync<Model.Device, Model.Prop, Model.Device>(
                query,
                (device, prop) =>
                {
                    if (!deviceDict.TryGetValue(device.Id, out var currentDevice))
                    {
                        currentDevice = device;
                        currentDevice.Props = new List<Model.Prop>();
                        deviceDict.Add(currentDevice.Id, currentDevice);
                    }
                    if(prop is not null) currentDevice.Props.Add(prop);
                    return currentDevice;
                },
                splitOn: $"{nameof(Model.Prop.Id)}" // Specify the column to split the results on
            );

            return result.Distinct().ToList();
        }

        public async Task<IReadOnlyList<Model.Device>> GetList(long userId)
        {
            var query =
                $@"SELECT 
                    d.""Id"" as {nameof(Model.Device.Id)}, 
                    d.""Name"" as {nameof(Model.Device.Name)}, 
                    d.""Description"" as {nameof(Model.Device.Description)},
                    d.""DeviceUrl"" as {nameof(Model.Device.DeviceUrl)},
                    d.""UserId"" as {nameof(Model.Device.UserId)},
                    p.""Id"" as {nameof(Model.Prop.Id)}, 
                    p.""Name"" as {nameof(Model.Prop.Name)},
                    p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)},
                    p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
                    p.""Type"" as {nameof(Model.Prop.Type)}
                    FROM ""Yuna_Devices"" d
                    LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId""
                    LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id""
                    WHERE d.""UserId"" = {userId}
                    AND NOT d.""IsDeleted""
                    ORDER BY d.""Id""";

            var deviceDict = new Dictionary<long, Model.Device>();
            var result = await _context.Connection.QueryAsync<Model.Device, Model.Prop, Model.Device>(
                query,
                (device, prop) =>
                {
                    if (!deviceDict.TryGetValue(device.Id, out var currentDevice))
                    {
                        currentDevice = device;
                        currentDevice.Props = new List<Model.Prop>();
                        deviceDict.Add(currentDevice.Id, currentDevice);
                    }
                    if (prop is not null) currentDevice.Props.Add(prop);
                    return currentDevice;
                },
                splitOn: $"{nameof(Model.Prop.Id)}" // Specify the column to split the results on
            );

            return result.Distinct().ToList();
        }

        public async Task<Model.Device?> Update(Model.Device device)
        {
            var query =
                $@"
                UPDATE public.""Yuna_Devices""
                SET ""Name"" = @Name,
                    ""Description"" = @Description,
                    ""DeviceUrl"" = @DeviceUrl,
                    ""UserId"" = @UserId
                WHERE ""Id"" = @Id
                AND NOT ""IsDeleted""
                returning *
                   ";

            return await _context.Connection.QuerySingleOrDefaultAsync<Model.Device>(query, device);
        }
    }
}