using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.SqlClient;
using System.Linq;
using XLAB2.Infrastructure;

namespace XLAB2
{
    internal sealed class TypeSizeDirectoryService
    {
        public IReadOnlyList<DirectoryLookupItem> LoadSpoiReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPOI AS Id, NMOI AS Name
FROM dbo.SPOI
ORDER BY NMOI, IDSPOI;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpnmtpReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPNMTP AS Id, NMTP AS Name
FROM dbo.SPNMTP
ORDER BY NMTP, IDSPNMTP;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpktReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPKT AS Id, NMKT AS Name
FROM dbo.SPKT
ORDER BY NMKT, IDSPKT;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpkiReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPKI AS Id, NMKI AS Name
FROM dbo.SPKI
ORDER BY NMKI, IDSPKI;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpkmmkReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPKMMK AS Id, KMMK AS Name
FROM dbo.SPKMMK
ORDER BY KMMK, IDSPKMMK;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpvdmcReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPVDMC AS Id, NMVDMC AS Name
FROM dbo.SPVDMC
ORDER BY NMVDMC, IDSPVDMC;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpvdmkReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPVDMK AS Id, NMVDMK AS Name
FROM dbo.SPVDMK
ORDER BY NMVDMK, IDSPVDMK;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpvdklReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPVDKL AS Id, NMVDKL AS Name
FROM dbo.SPVDKL
ORDER BY NMVDKL, IDSPVDKL;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadGrsiReferences()
        {
            return LoadLookupItems(@"
SELECT IDGRSI AS Id, NMGRSI AS Name
FROM dbo.GRSI
WHERE ISNULL(IsDeleted, 0) = 0
ORDER BY NMGRSI, IDGRSI;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadFrpdReferences()
        {
            return LoadLookupItems(@"
SELECT IDFRPD AS Id, NMFRPD AS Name
FROM dbo.FRPD
WHERE NULLIF(LTRIM(RTRIM(NMFRPD)), '') IS NOT NULL
ORDER BY NMFRPD, IDFRPD;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpkvReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPKV AS Id, NMKV AS Name
FROM dbo.SPKV
ORDER BY NMKV, IDSPKV;");
        }

        public IReadOnlyList<DirectoryLookupItem> LoadSpmpobReferences()
        {
            return LoadLookupItems(@"
SELECT IDSPMPOB AS Id, NMMPOB AS Name
FROM dbo.SPMPOB
ORDER BY NMMPOB, IDSPMPOB;");
        }

        public IReadOnlyList<TipsDirectoryItem> LoadTipsItems()
        {
            const string sql = @"
SELECT
    t.IDTIPS AS Id,
    t.IDSPOI AS MeasurementAreaId,
    areas.NMOI AS MeasurementAreaName,
    t.IDSPNMTP AS InstrumentNameId,
    names.NMTP AS InstrumentName,
    t.IDSPKT AS CategoryId,
    categories.NMKT AS CategoryName,
    t.IDSPKI AS DesignId,
    designs.NMKI AS DesignName,
    t.TP AS TypeName,
    t.SRSL AS ServiceLifeYears,
    t.PRMKGR AS RegistryPeriodMonths,
    t.NNTPGR AS RegistryTypeNumber,
    t.KDTPVNMS AS VniimsTypeCode,
    t.KDKMETR AS MetrControlCode,
    CAST(t.DSTP AS nvarchar(max)) AS Notes,
    t.SPECNZN AS IsSpecialPurpose,
    t.MKPRV AS IsMkPrimaryOnly
FROM dbo.TIPS t
JOIN dbo.SPOI areas ON areas.IDSPOI = t.IDSPOI
JOIN dbo.SPNMTP names ON names.IDSPNMTP = t.IDSPNMTP
LEFT JOIN dbo.SPKT categories ON categories.IDSPKT = t.IDSPKT
LEFT JOIN dbo.SPKI designs ON designs.IDSPKI = t.IDSPKI
ORDER BY areas.NMOI, names.NMTP, t.TP, t.IDTIPS;";

            var items = new List<TipsDirectoryItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new TipsDirectoryItem
                        {
                            Id = GetInt32(reader, "Id"),
                            MeasurementAreaId = GetInt32(reader, "MeasurementAreaId"),
                            MeasurementAreaName = GetString(reader, "MeasurementAreaName"),
                            InstrumentNameId = GetInt32(reader, "InstrumentNameId"),
                            InstrumentName = GetString(reader, "InstrumentName"),
                            CategoryId = GetNullableInt32(reader, "CategoryId"),
                            CategoryName = GetString(reader, "CategoryName"),
                            DesignId = GetNullableInt32(reader, "DesignId"),
                            DesignName = GetString(reader, "DesignName"),
                            TypeName = GetString(reader, "TypeName"),
                            ServiceLifeYears = GetNullableInt32(reader, "ServiceLifeYears"),
                            RegistryPeriodMonths = GetNullableInt32(reader, "RegistryPeriodMonths"),
                            RegistryTypeNumber = GetString(reader, "RegistryTypeNumber"),
                            VniimsTypeCode = GetNullableInt32(reader, "VniimsTypeCode"),
                            MetrControlCode = GetString(reader, "MetrControlCode"),
                            Notes = GetString(reader, "Notes"),
                            IsSpecialPurpose = GetNullableBoolean(reader, "IsSpecialPurpose"),
                            IsMkPrimaryOnly = GetNullableBoolean(reader, "IsMkPrimaryOnly")
                        });
                    }
                }
            }

            return items;
        }

        public int AddTipsItem(TipsDirectoryItem item)
        {
            var normalizedItem = NormalizeTipsItem(item);

            const string sql = @"
INSERT INTO dbo.TIPS
(
    IDSPOI,
    IDSPNMTP,
    IDSPKT,
    IDSPKI,
    TP,
    SRSL,
    PRMKGR,
    NNTPGR,
    KDTPVNMS,
    KDKMETR,
    DSTP,
    GUIDTIPS,
    SPECNZN,
    MKPRV
)
VALUES
(
    @MeasurementAreaId,
    @InstrumentNameId,
    @CategoryId,
    @DesignId,
    @TypeName,
    @ServiceLifeYears,
    @RegistryPeriodMonths,
    @RegistryTypeNumber,
    @VniimsTypeCode,
    @MetrControlCode,
    @Notes,
    @Guid,
    @IsSpecialPurpose,
    @IsMkPrimaryOnly
);

SELECT CAST(SCOPE_IDENTITY() AS int);";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTipsIsUnique(connection, normalizedItem, null);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@MeasurementAreaId", SqlDbType.Int).Value = normalizedItem.MeasurementAreaId;
                command.Parameters.Add("@InstrumentNameId", SqlDbType.Int).Value = normalizedItem.InstrumentNameId;
                AddNullableIntParameter(command, "@CategoryId", normalizedItem.CategoryId);
                AddNullableIntParameter(command, "@DesignId", normalizedItem.DesignId);
                command.Parameters.Add("@TypeName", SqlDbType.NVarChar, TipsDirectoryRules.TypeNameMaxLength).Value = normalizedItem.TypeName;
                AddNullableIntParameter(command, "@ServiceLifeYears", normalizedItem.ServiceLifeYears);
                AddNullableIntParameter(command, "@RegistryPeriodMonths", normalizedItem.RegistryPeriodMonths);
                AddNullableStringParameter(command, "@RegistryTypeNumber", SqlDbType.VarChar, TipsDirectoryRules.RegistryTypeNumberMaxLength, normalizedItem.RegistryTypeNumber);
                AddNullableIntParameter(command, "@VniimsTypeCode", normalizedItem.VniimsTypeCode);
                AddNullableStringParameter(command, "@MetrControlCode", SqlDbType.VarChar, TipsDirectoryRules.MetrControlCodeMaxLength, normalizedItem.MetrControlCode);
                AddNullableTextParameter(command, "@Notes", normalizedItem.Notes);
                command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
                AddNullableBooleanParameter(command, "@IsSpecialPurpose", normalizedItem.IsSpecialPurpose);
                AddNullableBooleanParameter(command, "@IsMkPrimaryOnly", normalizedItem.IsMkPrimaryOnly);

                try
                {
                    return Convert.ToInt32(command.ExecuteScalar());
                }
                catch (SqlException ex) when (IsTipsDuplicateViolation(ex))
                {
                    throw CreateTipsDuplicateException(normalizedItem.TypeName, ex);
                }
            }
        }

        public void UpdateTipsItem(TipsDirectoryItem item)
        {
            var normalizedItem = NormalizeTipsItem(item);
            if (normalizedItem.Id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TIPS РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ.");
            }

            const string sql = @"
UPDATE dbo.TIPS
SET IDSPOI = @MeasurementAreaId,
    IDSPNMTP = @InstrumentNameId,
    IDSPKT = @CategoryId,
    IDSPKI = @DesignId,
    TP = @TypeName,
    SRSL = @ServiceLifeYears,
    PRMKGR = @RegistryPeriodMonths,
    NNTPGR = @RegistryTypeNumber,
    KDTPVNMS = @VniimsTypeCode,
    KDKMETR = @MetrControlCode,
    DSTP = @Notes,
    SPECNZN = @IsSpecialPurpose,
    MKPRV = @IsMkPrimaryOnly
WHERE IDTIPS = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTipsIsUnique(connection, normalizedItem, normalizedItem.Id);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
                command.Parameters.Add("@MeasurementAreaId", SqlDbType.Int).Value = normalizedItem.MeasurementAreaId;
                command.Parameters.Add("@InstrumentNameId", SqlDbType.Int).Value = normalizedItem.InstrumentNameId;
                AddNullableIntParameter(command, "@CategoryId", normalizedItem.CategoryId);
                AddNullableIntParameter(command, "@DesignId", normalizedItem.DesignId);
                command.Parameters.Add("@TypeName", SqlDbType.NVarChar, TipsDirectoryRules.TypeNameMaxLength).Value = normalizedItem.TypeName;
                AddNullableIntParameter(command, "@ServiceLifeYears", normalizedItem.ServiceLifeYears);
                AddNullableIntParameter(command, "@RegistryPeriodMonths", normalizedItem.RegistryPeriodMonths);
                AddNullableStringParameter(command, "@RegistryTypeNumber", SqlDbType.VarChar, TipsDirectoryRules.RegistryTypeNumberMaxLength, normalizedItem.RegistryTypeNumber);
                AddNullableIntParameter(command, "@VniimsTypeCode", normalizedItem.VniimsTypeCode);
                AddNullableStringParameter(command, "@MetrControlCode", SqlDbType.VarChar, TipsDirectoryRules.MetrControlCodeMaxLength, normalizedItem.MetrControlCode);
                AddNullableTextParameter(command, "@Notes", normalizedItem.Notes);
                AddNullableBooleanParameter(command, "@IsSpecialPurpose", normalizedItem.IsSpecialPurpose);
                AddNullableBooleanParameter(command, "@IsMkPrimaryOnly", normalizedItem.IsMkPrimaryOnly);

                try
                {
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TIPS РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                    }
                }
                catch (SqlException ex) when (IsTipsDuplicateViolation(ex))
                {
                    throw CreateTipsDuplicateException(normalizedItem.TypeName, ex);
                }
            }
        }

        public DirectoryDeleteResult DeleteTipsItem(int id)
        {
            if (id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TIPS РґР»СЏ СѓРґР°Р»РµРЅРёСЏ.");
            }

            const string sql = @"
DELETE FROM dbo.TIPS
WHERE IDTIPS = @Id;

SELECT @@ROWCOUNT;";

            try
            {
                using (var connection = CreateConnection())
                {
                    connection.Open();
                    var blockers = LoadTipsDeleteBlockers(connection, id);
                    if (blockers.Count > 0)
                    {
                        return CreateBlockedDeleteResult(CreateTipsDeleteBlockedMessage(blockers));
                    }

                    using (var command = new SqlCommand(sql, connection))
                    {
                        command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                        command.Parameters.Add("@Id", SqlDbType.Int).Value = id;

                        if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                        {
                            throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TIPS РґР»СЏ СѓРґР°Р»РµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                        }
                    }

                    return CreateSuccessDeleteResult();
                }
            }
            catch (SqlException ex) when (ex.Number == 547)
            {
                return CreateBlockedDeleteResult(CreateTipsDeleteBlockedMessage(ex));
            }
        }

        public IReadOnlyList<TprzDirectoryItem> LoadTprzItems(int tipsId)
        {
            if (tipsId <= 0)
            {
                return Array.Empty<TprzDirectoryItem>();
            }

            const string sql = @"
SELECT
    t.IDTPRZ AS Id,
    t.IDTIPS AS TipsId,
    t.IDSPKMMK AS CompletenessId,
    completeness.KMMK AS CompletenessName,
    t.DPZN AS RangeText,
    t.HRTC AS AccuracyText,
    t.KDTRVNMS AS VniimsTypeSizeCode,
    t.NNGSRS AS RegistryTypeSizeNumber,
    t.KDSL AS ServiceCode
FROM dbo.TPRZ t
LEFT JOIN dbo.SPKMMK completeness ON completeness.IDSPKMMK = t.IDSPKMMK
WHERE t.IDTIPS = @TipsId
ORDER BY t.DPZN, t.HRTC, t.IDTPRZ;";

            var items = new List<TprzDirectoryItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = tipsId;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new TprzDirectoryItem
                        {
                            Id = GetInt32(reader, "Id"),
                            TipsId = GetInt32(reader, "TipsId"),
                            CompletenessId = GetNullableInt32(reader, "CompletenessId"),
                            CompletenessName = GetString(reader, "CompletenessName"),
                            RangeText = GetString(reader, "RangeText"),
                            AccuracyText = GetString(reader, "AccuracyText"),
                            VniimsTypeSizeCode = GetNullableInt32(reader, "VniimsTypeSizeCode"),
                            RegistryTypeSizeNumber = GetString(reader, "RegistryTypeSizeNumber"),
                            ServiceCode = GetString(reader, "ServiceCode")
                        });
                    }
                }
            }

            return items;
        }

        public IReadOnlyList<TprzDirectoryItem> LoadTprzSearchItems()
        {
            const string sql = @"
SELECT
    t.IDTPRZ AS Id,
    t.IDTIPS AS TipsId,
    t.IDSPKMMK AS CompletenessId,
    completeness.KMMK AS CompletenessName,
    t.DPZN AS RangeText,
    t.HRTC AS AccuracyText,
    t.KDTRVNMS AS VniimsTypeSizeCode,
    t.NNGSRS AS RegistryTypeSizeNumber,
    t.KDSL AS ServiceCode
FROM dbo.TPRZ t
LEFT JOIN dbo.SPKMMK completeness ON completeness.IDSPKMMK = t.IDSPKMMK
ORDER BY t.IDTIPS, t.DPZN, t.HRTC, t.IDTPRZ;";

            var items = new List<TprzDirectoryItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new TprzDirectoryItem
                        {
                            Id = GetInt32(reader, "Id"),
                            TipsId = GetInt32(reader, "TipsId"),
                            CompletenessId = GetNullableInt32(reader, "CompletenessId"),
                            CompletenessName = GetString(reader, "CompletenessName"),
                            RangeText = GetString(reader, "RangeText"),
                            AccuracyText = GetString(reader, "AccuracyText"),
                            VniimsTypeSizeCode = GetNullableInt32(reader, "VniimsTypeSizeCode"),
                            RegistryTypeSizeNumber = GetString(reader, "RegistryTypeSizeNumber"),
                            ServiceCode = GetString(reader, "ServiceCode")
                        });
                    }
                }
            }

            return items;
        }

        public int AddTprzItem(TprzDirectoryItem item)
        {
            var normalizedItem = NormalizeTprzItem(item);

            const string sql = @"
INSERT INTO dbo.TPRZ
(
    IDTIPS,
    IDSPKMMK,
    DPZN,
    HRTC,
    KDTRVNMS,
    NNGSRS,
    KDSL,
    GUIDTPRZ
)
VALUES
(
    @TipsId,
    @CompletenessId,
    @RangeText,
    @AccuracyText,
    @VniimsTypeSizeCode,
    @RegistryTypeSizeNumber,
    @ServiceCode,
    @Guid
);

SELECT CAST(SCOPE_IDENTITY() AS int);";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTprzIsUnique(connection, normalizedItem, null);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = normalizedItem.TipsId;
                AddNullableIntParameter(command, "@CompletenessId", normalizedItem.CompletenessId);
                command.Parameters.Add("@RangeText", SqlDbType.NVarChar, TprzDirectoryRules.RangeTextMaxLength).Value = normalizedItem.RangeText;
                command.Parameters.Add("@AccuracyText", SqlDbType.NVarChar, TprzDirectoryRules.AccuracyTextMaxLength).Value = normalizedItem.AccuracyText;
                AddNullableIntParameter(command, "@VniimsTypeSizeCode", normalizedItem.VniimsTypeSizeCode);
                AddNullableStringParameter(command, "@RegistryTypeSizeNumber", SqlDbType.VarChar, TprzDirectoryRules.RegistryTypeSizeNumberMaxLength, normalizedItem.RegistryTypeSizeNumber);
                AddNullableStringParameter(command, "@ServiceCode", SqlDbType.VarChar, TprzDirectoryRules.ServiceCodeMaxLength, normalizedItem.ServiceCode);
                command.Parameters.Add("@Guid", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();

                try
                {
                    return Convert.ToInt32(command.ExecuteScalar());
                }
                catch (SqlException ex) when (IsTprzDuplicateViolation(ex))
                {
                    throw CreateTprzDuplicateException(normalizedItem, ex);
                }
            }
        }

        public void UpdateTprzItem(TprzDirectoryItem item)
        {
            var normalizedItem = NormalizeTprzItem(item);
            if (normalizedItem.Id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPRZ РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ.");
            }

            const string sql = @"
UPDATE dbo.TPRZ
SET IDTIPS = @TipsId,
    IDSPKMMK = @CompletenessId,
    DPZN = @RangeText,
    HRTC = @AccuracyText,
    KDTRVNMS = @VniimsTypeSizeCode,
    NNGSRS = @RegistryTypeSizeNumber,
    KDSL = @ServiceCode
WHERE IDTPRZ = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTprzIsUnique(connection, normalizedItem, normalizedItem.Id);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = normalizedItem.TipsId;
                AddNullableIntParameter(command, "@CompletenessId", normalizedItem.CompletenessId);
                command.Parameters.Add("@RangeText", SqlDbType.NVarChar, TprzDirectoryRules.RangeTextMaxLength).Value = normalizedItem.RangeText;
                command.Parameters.Add("@AccuracyText", SqlDbType.NVarChar, TprzDirectoryRules.AccuracyTextMaxLength).Value = normalizedItem.AccuracyText;
                AddNullableIntParameter(command, "@VniimsTypeSizeCode", normalizedItem.VniimsTypeSizeCode);
                AddNullableStringParameter(command, "@RegistryTypeSizeNumber", SqlDbType.VarChar, TprzDirectoryRules.RegistryTypeSizeNumberMaxLength, normalizedItem.RegistryTypeSizeNumber);
                AddNullableStringParameter(command, "@ServiceCode", SqlDbType.VarChar, TprzDirectoryRules.ServiceCodeMaxLength, normalizedItem.ServiceCode);

                try
                {
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPRZ РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                    }
                }
                catch (SqlException ex) when (IsTprzDuplicateViolation(ex))
                {
                    throw CreateTprzDuplicateException(normalizedItem, ex);
                }
            }
        }

        public DirectoryDeleteResult DeleteTprzItem(int id)
        {
            if (id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPRZ РґР»СЏ СѓРґР°Р»РµРЅРёСЏ.");
            }

            const string sql = @"
DELETE FROM dbo.TPRZ
WHERE IDTPRZ = @Id;

SELECT @@ROWCOUNT;";

            try
            {
                using (var connection = CreateConnection())
                {
                    connection.Open();
                    var blockers = LoadTprzDeleteBlockers(connection, id);
                    if (blockers.Count > 0)
                    {
                        return CreateBlockedDeleteResult(CreateTprzDeleteBlockedMessage(blockers));
                    }

                    using (var command = new SqlCommand(sql, connection))
                    {
                        command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                        command.Parameters.Add("@Id", SqlDbType.Int).Value = id;

                        if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                        {
                            throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPRZ РґР»СЏ СѓРґР°Р»РµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                        }
                    }

                    return CreateSuccessDeleteResult();
                }
            }
            catch (SqlException ex) when (ex.Number == 547)
            {
                return CreateBlockedDeleteResult(CreateTprzDeleteBlockedMessage(ex));
            }
        }

        public IReadOnlyList<TprmcpDirectoryItem> LoadTprmcpItems(int typeSizeId)
        {
            if (typeSizeId <= 0)
            {
                return Array.Empty<TprmcpDirectoryItem>();
            }

            const string sql = @"
SELECT
    t.IDTPRMCP AS Id,
    t.IDTPRZ AS TypeSizeId,
    t.IDSPVDMC AS CycleId,
    cycle.NMVDMC AS CycleName,
    t.IDGRSI AS GroupId,
    groups.NMGRSI AS GroupName,
    t.PRMK AS PeriodMonths,
    CAST(t.KM AS nvarchar(max)) AS Comment
FROM dbo.TPRMCP t
LEFT JOIN dbo.SPVDMC cycle ON cycle.IDSPVDMC = t.IDSPVDMC
LEFT JOIN dbo.GRSI groups ON groups.IDGRSI = t.IDGRSI
WHERE t.IDTPRZ = @TypeSizeId
ORDER BY t.PRMK, cycle.NMVDMC, groups.NMGRSI, t.IDTPRMCP;";

            var items = new List<TprmcpDirectoryItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new TprmcpDirectoryItem
                        {
                            Id = GetInt32(reader, "Id"),
                            TypeSizeId = GetInt32(reader, "TypeSizeId"),
                            CycleId = GetNullableInt32(reader, "CycleId"),
                            CycleName = GetString(reader, "CycleName"),
                            GroupId = GetNullableInt32(reader, "GroupId"),
                            GroupName = GetString(reader, "GroupName"),
                            PeriodMonths = GetInt32(reader, "PeriodMonths"),
                            Comment = GetString(reader, "Comment")
                        });
                    }
                }
            }

            return items;
        }

        public int AddTprmcpItem(TprmcpDirectoryItem item)
        {
            var normalizedItem = NormalizeTprmcpItem(item);

            const string sql = @"
INSERT INTO dbo.TPRMCP
(
    IDTPRZ,
    IDSPVDMC,
    IDGRSI,
    PRMK,
    KM
)
VALUES
(
    @TypeSizeId,
    @CycleId,
    @GroupId,
    @PeriodMonths,
    @Comment
);

SELECT CAST(SCOPE_IDENTITY() AS int);";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTprmcpIsUnique(connection, normalizedItem, null);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
                AddNullableIntParameter(command, "@CycleId", normalizedItem.CycleId);
                AddNullableIntParameter(command, "@GroupId", normalizedItem.GroupId);
                command.Parameters.Add("@PeriodMonths", SqlDbType.Int).Value = normalizedItem.PeriodMonths;
                AddNullableTextParameter(command, "@Comment", normalizedItem.Comment);

                try
                {
                    return Convert.ToInt32(command.ExecuteScalar());
                }
                catch (SqlException ex) when (IsTprmcpDuplicateViolation(ex))
                {
                    throw CreateTprmcpDuplicateException(ex);
                }
                catch (SqlException ex) when (IsTprmcpCycleRuleViolation(ex))
                {
                    throw CreateTprmcpCycleRuleException(ex);
                }
            }
        }

        public void UpdateTprmcpItem(TprmcpDirectoryItem item)
        {
            var normalizedItem = NormalizeTprmcpItem(item);
            if (normalizedItem.Id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPRMCP РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ.");
            }

            const string sql = @"
UPDATE dbo.TPRMCP
SET IDTPRZ = @TypeSizeId,
    IDSPVDMC = @CycleId,
    IDGRSI = @GroupId,
    PRMK = @PeriodMonths,
    KM = @Comment
WHERE IDTPRMCP = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTprmcpIsUnique(connection, normalizedItem, normalizedItem.Id);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
                AddNullableIntParameter(command, "@CycleId", normalizedItem.CycleId);
                AddNullableIntParameter(command, "@GroupId", normalizedItem.GroupId);
                command.Parameters.Add("@PeriodMonths", SqlDbType.Int).Value = normalizedItem.PeriodMonths;
                AddNullableTextParameter(command, "@Comment", normalizedItem.Comment);

                try
                {
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPRMCP РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                    }
                }
                catch (SqlException ex) when (IsTprmcpDuplicateViolation(ex))
                {
                    throw CreateTprmcpDuplicateException(ex);
                }
                catch (SqlException ex) when (IsTprmcpCycleRuleViolation(ex))
                {
                    throw CreateTprmcpCycleRuleException(ex);
                }
            }
        }

        public DirectoryDeleteResult DeleteTprmcpItem(int id)
        {
            if (id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPRMCP РґР»СЏ СѓРґР°Р»РµРЅРёСЏ.");
            }

            const string sql = @"
DELETE FROM dbo.TPRMCP
WHERE IDTPRMCP = @Id;

SELECT @@ROWCOUNT;";

            try
            {
                using (var connection = CreateConnection())
                {
                    connection.Open();
                    var blockers = LoadTprmcpDeleteBlockers(connection, id);
                    if (blockers.Count > 0)
                    {
                        return CreateBlockedDeleteResult(CreateTprmcpDeleteBlockedMessage(blockers));
                    }

                    using (var command = new SqlCommand(sql, connection))
                    {
                        command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                        command.Parameters.Add("@Id", SqlDbType.Int).Value = id;

                        if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                        {
                            throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPRMCP РґР»СЏ СѓРґР°Р»РµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                        }
                    }

                    return CreateSuccessDeleteResult();
                }
            }
            catch (SqlException ex) when (ex.Number == 547)
            {
                return CreateBlockedDeleteResult(CreateTprmcpDeleteBlockedMessage(ex));
            }
        }

        public IReadOnlyList<TprmkDirectoryItem> LoadTprmkItems(int typeSizeId)
        {
            if (typeSizeId <= 0)
            {
                return Array.Empty<TprmkDirectoryItem>();
            }

            const string sql = @"
SELECT
    t.IDTPRMK AS Id,
    t.IDTPRZ AS TypeSizeId,
    t.IDSPVDMK AS VerificationTypeId,
    verificationType.NMVDMK AS VerificationTypeName,
    t.IDFRPD AS OrganizationId,
    organization.NMFRPD AS OrganizationName,
    t.IDSPKV AS QualificationId,
    qualification.NMKV AS QualificationName,
    t.IDGRSI AS GroupId,
    groups.NMGRSI AS GroupName,
    t.IDSPMPOB AS PlaceId,
    places.NMMPOB AS PlaceName,
    t.CNM AS Cost,
    t.CNMD AS ExtraCost,
    t.NCSRM AS RushMarkup,
    t.NRVRM AS TimeNormHours,
    t.KDNRVRM AS TimeNormCode,
    t.KLPV AS VerifierCount,
    t.KDPV AS VerificationCode,
    t.NRVRMND AS NormDocHours
FROM dbo.TPRMK t
JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = t.IDSPVDMK
JOIN dbo.FRPD organization ON organization.IDFRPD = t.IDFRPD
LEFT JOIN dbo.SPKV qualification ON qualification.IDSPKV = t.IDSPKV
LEFT JOIN dbo.GRSI groups ON groups.IDGRSI = t.IDGRSI
LEFT JOIN dbo.SPMPOB places ON places.IDSPMPOB = t.IDSPMPOB
WHERE t.IDTPRZ = @TypeSizeId
ORDER BY verificationType.NMVDMK, organization.NMFRPD, t.IDTPRMK;";

            var items = new List<TprmkDirectoryItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = typeSizeId;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new TprmkDirectoryItem
                        {
                            Id = GetInt32(reader, "Id"),
                            TypeSizeId = GetInt32(reader, "TypeSizeId"),
                            VerificationTypeId = GetInt32(reader, "VerificationTypeId"),
                            VerificationTypeName = GetString(reader, "VerificationTypeName"),
                            OrganizationId = GetInt32(reader, "OrganizationId"),
                            OrganizationName = GetString(reader, "OrganizationName"),
                            QualificationId = GetNullableInt32(reader, "QualificationId"),
                            QualificationName = GetString(reader, "QualificationName"),
                            GroupId = GetNullableInt32(reader, "GroupId"),
                            GroupName = GetString(reader, "GroupName"),
                            PlaceId = GetNullableInt32(reader, "PlaceId"),
                            PlaceName = GetString(reader, "PlaceName"),
                            Cost = GetNullableDecimal(reader, "Cost"),
                            ExtraCost = GetNullableDecimal(reader, "ExtraCost"),
                            RushMarkup = GetNullableDecimal(reader, "RushMarkup"),
                            TimeNormHours = GetNullableDecimal(reader, "TimeNormHours"),
                            TimeNormCode = GetString(reader, "TimeNormCode"),
                            VerifierCount = GetInt32(reader, "VerifierCount"),
                            VerificationCode = GetString(reader, "VerificationCode"),
                            NormDocHours = GetNullableDecimal(reader, "NormDocHours")
                        });
                    }
                }
            }

            return items;
        }

        public int AddTprmkItem(TprmkDirectoryItem item)
        {
            var normalizedItem = NormalizeTprmkItem(item);

            const string sql = @"
INSERT INTO dbo.TPRMK
(
    IDTPRZ,
    IDSPVDMK,
    IDFRPD,
    IDSPKV,
    IDGRSI,
    IDSPMPOB,
    CNM,
    CNMD,
    NCSRM,
    NRVRM,
    KDNRVRM,
    KLPV,
    KDPV,
    NRVRMND
)
VALUES
(
    @TypeSizeId,
    @VerificationTypeId,
    @OrganizationId,
    @QualificationId,
    @GroupId,
    @PlaceId,
    @Cost,
    @ExtraCost,
    @RushMarkup,
    @TimeNormHours,
    @TimeNormCode,
    @VerifierCount,
    @VerificationCode,
    @NormDocHours
);

SELECT CAST(SCOPE_IDENTITY() AS int);";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTprmkIsUnique(connection, normalizedItem, null);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
                command.Parameters.Add("@VerificationTypeId", SqlDbType.Int).Value = normalizedItem.VerificationTypeId;
                command.Parameters.Add("@OrganizationId", SqlDbType.Int).Value = normalizedItem.OrganizationId;
                AddNullableIntParameter(command, "@QualificationId", normalizedItem.QualificationId);
                AddNullableIntParameter(command, "@GroupId", normalizedItem.GroupId);
                AddNullableIntParameter(command, "@PlaceId", normalizedItem.PlaceId);
                AddNullableMoneyParameter(command, "@Cost", normalizedItem.Cost);
                AddNullableMoneyParameter(command, "@ExtraCost", normalizedItem.ExtraCost);
                AddNullableMoneyParameter(command, "@RushMarkup", normalizedItem.RushMarkup);
                AddNullableDecimal62Parameter(command, "@TimeNormHours", normalizedItem.TimeNormHours);
                AddNullableStringParameter(command, "@TimeNormCode", SqlDbType.VarChar, TprmkDirectoryRules.TimeNormCodeMaxLength, normalizedItem.TimeNormCode);
                command.Parameters.Add("@VerifierCount", SqlDbType.Int).Value = normalizedItem.VerifierCount;
                AddNullableStringParameter(command, "@VerificationCode", SqlDbType.VarChar, TprmkDirectoryRules.VerificationCodeMaxLength, normalizedItem.VerificationCode);
                AddNullableDecimal62Parameter(command, "@NormDocHours", normalizedItem.NormDocHours);

                try
                {
                    return Convert.ToInt32(command.ExecuteScalar());
                }
                catch (SqlException ex) when (IsTprmkDuplicateViolation(ex))
                {
                    throw CreateTprmkDuplicateException(ex);
                }
            }
        }

        public void UpdateTprmkItem(TprmkDirectoryItem item)
        {
            var normalizedItem = NormalizeTprmkItem(item);
            if (normalizedItem.Id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPRMK РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ.");
            }

            const string sql = @"
UPDATE dbo.TPRMK
SET IDTPRZ = @TypeSizeId,
    IDSPVDMK = @VerificationTypeId,
    IDFRPD = @OrganizationId,
    IDSPKV = @QualificationId,
    IDGRSI = @GroupId,
    IDSPMPOB = @PlaceId,
    CNM = @Cost,
    CNMD = @ExtraCost,
    NCSRM = @RushMarkup,
    NRVRM = @TimeNormHours,
    KDNRVRM = @TimeNormCode,
    KLPV = @VerifierCount,
    KDPV = @VerificationCode,
    NRVRMND = @NormDocHours
WHERE IDTPRMK = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTprmkIsUnique(connection, normalizedItem, normalizedItem.Id);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = normalizedItem.TypeSizeId;
                command.Parameters.Add("@VerificationTypeId", SqlDbType.Int).Value = normalizedItem.VerificationTypeId;
                command.Parameters.Add("@OrganizationId", SqlDbType.Int).Value = normalizedItem.OrganizationId;
                AddNullableIntParameter(command, "@QualificationId", normalizedItem.QualificationId);
                AddNullableIntParameter(command, "@GroupId", normalizedItem.GroupId);
                AddNullableIntParameter(command, "@PlaceId", normalizedItem.PlaceId);
                AddNullableMoneyParameter(command, "@Cost", normalizedItem.Cost);
                AddNullableMoneyParameter(command, "@ExtraCost", normalizedItem.ExtraCost);
                AddNullableMoneyParameter(command, "@RushMarkup", normalizedItem.RushMarkup);
                AddNullableDecimal62Parameter(command, "@TimeNormHours", normalizedItem.TimeNormHours);
                AddNullableStringParameter(command, "@TimeNormCode", SqlDbType.VarChar, TprmkDirectoryRules.TimeNormCodeMaxLength, normalizedItem.TimeNormCode);
                command.Parameters.Add("@VerifierCount", SqlDbType.Int).Value = normalizedItem.VerifierCount;
                AddNullableStringParameter(command, "@VerificationCode", SqlDbType.VarChar, TprmkDirectoryRules.VerificationCodeMaxLength, normalizedItem.VerificationCode);
                AddNullableDecimal62Parameter(command, "@NormDocHours", normalizedItem.NormDocHours);

                try
                {
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPRMK РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                    }
                }
                catch (SqlException ex) when (IsTprmkDuplicateViolation(ex))
                {
                    throw CreateTprmkDuplicateException(ex);
                }
            }
        }

        public DirectoryDeleteResult DeleteTprmkItem(int id)
        {
            if (id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPRMK РґР»СЏ СѓРґР°Р»РµРЅРёСЏ.");
            }

            const string sql = @"
DELETE FROM dbo.TPRMK
WHERE IDTPRMK = @Id;

SELECT @@ROWCOUNT;";

            try
            {
                using (var connection = CreateConnection())
                {
                    connection.Open();
                    var blockers = LoadTprmkDeleteBlockers(connection, id);
                    if (blockers.Count > 0)
                    {
                        return CreateBlockedDeleteResult(CreateTprmkDeleteBlockedMessage(blockers));
                    }

                    using (var command = new SqlCommand(sql, connection))
                    {
                        command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                        command.Parameters.Add("@Id", SqlDbType.Int).Value = id;

                        if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                        {
                            throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPRMK РґР»СЏ СѓРґР°Р»РµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                        }
                    }

                    return CreateSuccessDeleteResult();
                }
            }
            catch (SqlException ex) when (ex.Number == 547)
            {
                return CreateBlockedDeleteResult(CreateTprmkDeleteBlockedMessage(ex));
            }
        }

        public IReadOnlyList<TpvdklDirectoryItem> LoadTpvdklItems(int tipsId)
        {
            if (tipsId <= 0)
            {
                return Array.Empty<TpvdklDirectoryItem>();
            }

            const string sql = @"
SELECT
    t.IDTPVDKL AS Id,
    t.IDTIPS AS TipsId,
    t.IDSPVDMK AS VerificationTypeId,
    verificationType.NMVDMK AS VerificationTypeName,
    t.IDSPVDKL AS StampKindId,
    stampKind.NMVDKL AS StampKindName
FROM dbo.TPVDKL t
JOIN dbo.SPVDMK verificationType ON verificationType.IDSPVDMK = t.IDSPVDMK
JOIN dbo.SPVDKL stampKind ON stampKind.IDSPVDKL = t.IDSPVDKL
WHERE t.IDTIPS = @TipsId
ORDER BY verificationType.NMVDMK, stampKind.NMVDKL, t.IDTPVDKL;";

            var items = new List<TpvdklDirectoryItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = tipsId;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new TpvdklDirectoryItem
                        {
                            Id = GetInt32(reader, "Id"),
                            TipsId = GetInt32(reader, "TipsId"),
                            VerificationTypeId = GetInt32(reader, "VerificationTypeId"),
                            VerificationTypeName = GetString(reader, "VerificationTypeName"),
                            StampKindId = GetInt32(reader, "StampKindId"),
                            StampKindName = GetString(reader, "StampKindName")
                        });
                    }
                }
            }

            return items;
        }

        public int AddTpvdklItem(TpvdklDirectoryItem item)
        {
            var normalizedItem = NormalizeTpvdklItem(item);

            const string sql = @"
INSERT INTO dbo.TPVDKL
(
    IDTIPS,
    IDSPVDKL,
    IDSPVDMK
)
VALUES
(
    @TipsId,
    @StampKindId,
    @VerificationTypeId
);

SELECT CAST(SCOPE_IDENTITY() AS int);";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTpvdklIsUnique(connection, normalizedItem, null);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = normalizedItem.TipsId;
                command.Parameters.Add("@StampKindId", SqlDbType.Int).Value = normalizedItem.StampKindId;
                command.Parameters.Add("@VerificationTypeId", SqlDbType.Int).Value = normalizedItem.VerificationTypeId;

                try
                {
                    return Convert.ToInt32(command.ExecuteScalar());
                }
                catch (SqlException ex) when (IsTpvdklDuplicateViolation(ex))
                {
                    throw CreateTpvdklDuplicateException(ex);
                }
            }
        }

        public void UpdateTpvdklItem(TpvdklDirectoryItem item)
        {
            var normalizedItem = NormalizeTpvdklItem(item);
            if (normalizedItem.Id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPVDKL РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ.");
            }

            const string sql = @"
UPDATE dbo.TPVDKL
SET IDTIPS = @TipsId,
    IDSPVDKL = @StampKindId,
    IDSPVDMK = @VerificationTypeId
WHERE IDTPVDKL = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsureTpvdklIsUnique(connection, normalizedItem, normalizedItem.Id);
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = normalizedItem.Id;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = normalizedItem.TipsId;
                command.Parameters.Add("@StampKindId", SqlDbType.Int).Value = normalizedItem.StampKindId;
                command.Parameters.Add("@VerificationTypeId", SqlDbType.Int).Value = normalizedItem.VerificationTypeId;

                try
                {
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPVDKL РґР»СЏ РёР·РјРµРЅРµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                    }
                }
                catch (SqlException ex) when (IsTpvdklDuplicateViolation(ex))
                {
                    throw CreateTpvdklDuplicateException(ex);
                }
            }
        }

        public DirectoryDeleteResult DeleteTpvdklItem(int id)
        {
            if (id <= 0)
            {
                throw new InvalidOperationException("РќРµ РІС‹Р±СЂР°РЅР° Р·Р°РїРёСЃСЊ TPVDKL РґР»СЏ СѓРґР°Р»РµРЅРёСЏ.");
            }

            const string sql = @"
DELETE FROM dbo.TPVDKL
WHERE IDTPVDKL = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = id;

                if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                {
                    throw new InvalidOperationException("Р—Р°РїРёСЃСЊ TPVDKL РґР»СЏ СѓРґР°Р»РµРЅРёСЏ РЅРµ РЅР°Р№РґРµРЅР°.");
                }
            }

            return CreateSuccessDeleteResult();
        }

        private static TipsDirectoryItem NormalizeTipsItem(TipsDirectoryItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            return new TipsDirectoryItem
            {
                Id = item.Id,
                MeasurementAreaId = NormalizeRequiredId(item.MeasurementAreaId, "РќРµ СѓРєР°Р·Р°РЅР° РѕР±Р»Р°СЃС‚СЊ РёР·РјРµСЂРµРЅРёР№."),
                InstrumentNameId = NormalizeRequiredId(item.InstrumentNameId, "РќРµ СѓРєР°Р·Р°РЅРѕ РЅР°РёРјРµРЅРѕРІР°РЅРёРµ С‚РёРїР° РЎР."),
                CategoryId = NormalizeOptionalId(item.CategoryId),
                DesignId = NormalizeOptionalId(item.DesignId),
                TypeName = NormalizeRequiredString(item.TypeName, "РќРµ СѓРєР°Р·Р°РЅ С‚РёРї РЎР.", TipsDirectoryRules.TypeNameMaxLength, "РўРёРї РЎР РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                ServiceLifeYears = item.ServiceLifeYears,
                RegistryPeriodMonths = item.RegistryPeriodMonths,
                RegistryTypeNumber = NormalizeOptionalString(item.RegistryTypeNumber, TipsDirectoryRules.RegistryTypeNumberMaxLength, "РќРѕРјРµСЂ РїРѕ Р“РѕСЃСЂРµРµСЃС‚СЂСѓ РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                VniimsTypeCode = item.VniimsTypeCode,
                MetrControlCode = NormalizeOptionalString(item.MetrControlCode, TipsDirectoryRules.MetrControlCodeMaxLength, "РљРѕРґ РђРРЎ \"РњРµС‚СЂРєРѕРЅС‚СЂРѕР»СЊ\" РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                Notes = NormalizeOptionalText(item.Notes),
                IsSpecialPurpose = item.IsSpecialPurpose,
                IsMkPrimaryOnly = item.IsMkPrimaryOnly
            };
        }

        private static TprzDirectoryItem NormalizeTprzItem(TprzDirectoryItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            return new TprzDirectoryItem
            {
                Id = item.Id,
                TipsId = NormalizeRequiredId(item.TipsId, "РќРµ РІС‹Р±СЂР°РЅ СЂРѕРґРёС‚РµР»СЊСЃРєРёР№ С‚РёРї РЎР."),
                CompletenessId = NormalizeOptionalId(item.CompletenessId),
                RangeText = NormalizeRequiredString(item.RangeText, "РќРµ СѓРєР°Р·Р°РЅ РґРёР°РїР°Р·РѕРЅ.", TprzDirectoryRules.RangeTextMaxLength, "Р”РёР°РїР°Р·РѕРЅ РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                AccuracyText = NormalizeRequiredString(item.AccuracyText, "РќРµ СѓРєР°Р·Р°РЅР° С…Р°СЂР°РєС‚РµСЂРёСЃС‚РёРєР° С‚РѕС‡РЅРѕСЃС‚Рё.", TprzDirectoryRules.AccuracyTextMaxLength, "РҐР°СЂР°РєС‚РµСЂРёСЃС‚РёРєР° С‚РѕС‡РЅРѕСЃС‚Рё РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                VniimsTypeSizeCode = item.VniimsTypeSizeCode,
                RegistryTypeSizeNumber = NormalizeOptionalString(item.RegistryTypeSizeNumber, TprzDirectoryRules.RegistryTypeSizeNumberMaxLength, "РќРѕРјРµСЂ С‚РёРїРѕСЂР°Р·РјРµСЂР° РїРѕ Р“РѕСЃСЂРµРµСЃС‚СЂСѓ РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                ServiceCode = NormalizeOptionalString(item.ServiceCode, TprzDirectoryRules.ServiceCodeMaxLength, "РЎР»СѓР¶РµР±РЅС‹Р№ РєРѕРґ РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ.")
            };
        }

        private static TprmcpDirectoryItem NormalizeTprmcpItem(TprmcpDirectoryItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            return new TprmcpDirectoryItem
            {
                Id = item.Id,
                TypeSizeId = NormalizeRequiredId(item.TypeSizeId, "РќРµ РІС‹Р±СЂР°РЅ СЂРѕРґРёС‚РµР»СЊСЃРєРёР№ С‚РёРїРѕСЂР°Р·РјРµСЂ."),
                CycleId = NormalizeOptionalId(item.CycleId),
                GroupId = NormalizeOptionalId(item.GroupId),
                PeriodMonths = item.PeriodMonths,
                Comment = NormalizeOptionalText(item.Comment)
            };
        }

        private static TprmkDirectoryItem NormalizeTprmkItem(TprmkDirectoryItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            return new TprmkDirectoryItem
            {
                Id = item.Id,
                TypeSizeId = NormalizeRequiredId(item.TypeSizeId, "РќРµ РІС‹Р±СЂР°РЅ СЂРѕРґРёС‚РµР»СЊСЃРєРёР№ С‚РёРїРѕСЂР°Р·РјРµСЂ."),
                VerificationTypeId = NormalizeRequiredId(item.VerificationTypeId, "РќРµ СѓРєР°Р·Р°РЅ РІРёРґ РњРљ."),
                OrganizationId = NormalizeRequiredId(item.OrganizationId, "РќРµ СѓРєР°Р·Р°РЅР° РѕСЂРіР°РЅРёР·Р°С†РёСЏ/РїРѕРґСЂР°Р·РґРµР»РµРЅРёРµ."),
                QualificationId = NormalizeOptionalId(item.QualificationId),
                GroupId = NormalizeOptionalId(item.GroupId),
                PlaceId = NormalizeOptionalId(item.PlaceId),
                Cost = item.Cost,
                ExtraCost = item.ExtraCost,
                RushMarkup = item.RushMarkup,
                TimeNormHours = item.TimeNormHours,
                TimeNormCode = NormalizeOptionalString(item.TimeNormCode, TprmkDirectoryRules.TimeNormCodeMaxLength, "РљРѕРґ РЅРѕСЂРјС‹ РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                VerifierCount = item.VerifierCount,
                VerificationCode = NormalizeOptionalString(item.VerificationCode, TprmkDirectoryRules.VerificationCodeMaxLength, "РљРѕРґ РїРѕРІРµСЂРєРё РїСЂРµРІС‹С€Р°РµС‚ РґРѕРїСѓСЃС‚РёРјСѓСЋ РґР»РёРЅСѓ."),
                NormDocHours = item.NormDocHours
            };
        }

        private static TpvdklDirectoryItem NormalizeTpvdklItem(TpvdklDirectoryItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            return new TpvdklDirectoryItem
            {
                Id = item.Id,
                TipsId = NormalizeRequiredId(item.TipsId, "РќРµ РІС‹Р±СЂР°РЅ СЂРѕРґРёС‚РµР»СЊСЃРєРёР№ С‚РёРї РЎР."),
                VerificationTypeId = NormalizeRequiredId(item.VerificationTypeId, "РќРµ РІС‹Р±СЂР°РЅ РІРёРґ РњРљ."),
                StampKindId = NormalizeRequiredId(item.StampKindId, "РќРµ РІС‹Р±СЂР°РЅ РІРёРґ РєР»РµР№РјР°.")
            };
        }

        private static void EnsureTipsIsUnique(SqlConnection connection, TipsDirectoryItem item, int? excludeId)
        {
            const string sql = @"
SELECT TOP (1) IDTIPS
FROM dbo.TIPS
WHERE IDSPNMTP = @InstrumentNameId
  AND TP = @TypeName
  AND (@ExcludeId IS NULL OR IDTIPS <> @ExcludeId);";

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@InstrumentNameId", SqlDbType.Int).Value = item.InstrumentNameId;
                command.Parameters.Add("@TypeName", SqlDbType.NVarChar, TipsDirectoryRules.TypeNameMaxLength).Value = item.TypeName;
                command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;

                if (command.ExecuteScalar() != null)
                {
                    throw CreateTipsDuplicateException(item.TypeName, null);
                }
            }
        }

        private static void EnsureTprzIsUnique(SqlConnection connection, TprzDirectoryItem item, int? excludeId)
        {
            const string sql = @"
SELECT TOP (1) IDTPRZ
FROM dbo.TPRZ
WHERE IDTIPS = @TipsId
  AND DPZN = @RangeText
  AND HRTC = @AccuracyText
  AND (@ExcludeId IS NULL OR IDTPRZ <> @ExcludeId);";

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = item.TipsId;
                command.Parameters.Add("@RangeText", SqlDbType.NVarChar, TprzDirectoryRules.RangeTextMaxLength).Value = item.RangeText;
                command.Parameters.Add("@AccuracyText", SqlDbType.NVarChar, TprzDirectoryRules.AccuracyTextMaxLength).Value = item.AccuracyText;
                command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;

                if (command.ExecuteScalar() != null)
                {
                    throw CreateTprzDuplicateException(item, null);
                }
            }
        }

        private static void EnsureTprmcpIsUnique(SqlConnection connection, TprmcpDirectoryItem item, int? excludeId)
        {
            const string sql = @"
SELECT TOP (1) IDTPRMCP
FROM dbo.TPRMCP
WHERE IDTPRZ = @TypeSizeId
  AND ((IDSPVDMC = @CycleId) OR (IDSPVDMC IS NULL AND @CycleId IS NULL))
  AND ((IDGRSI = @GroupId) OR (IDGRSI IS NULL AND @GroupId IS NULL))
  AND PRMK = @PeriodMonths
  AND (@ExcludeId IS NULL OR IDTPRMCP <> @ExcludeId);";

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = item.TypeSizeId;
                command.Parameters.Add("@CycleId", SqlDbType.Int).Value = (object)item.CycleId ?? DBNull.Value;
                command.Parameters.Add("@GroupId", SqlDbType.Int).Value = (object)item.GroupId ?? DBNull.Value;
                command.Parameters.Add("@PeriodMonths", SqlDbType.Int).Value = item.PeriodMonths;
                command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;

                if (command.ExecuteScalar() != null)
                {
                    throw CreateTprmcpDuplicateException(null);
                }
            }
        }

        private static void EnsureTprmkIsUnique(SqlConnection connection, TprmkDirectoryItem item, int? excludeId)
        {
            const string sql = @"
SELECT TOP (1) IDTPRMK
FROM dbo.TPRMK
WHERE IDTPRZ = @TypeSizeId
  AND IDSPVDMK = @VerificationTypeId
  AND IDFRPD = @OrganizationId
  AND ((IDSPMPOB = @PlaceId) OR (IDSPMPOB IS NULL AND @PlaceId IS NULL))
  AND ((IDGRSI = @GroupId) OR (IDGRSI IS NULL AND @GroupId IS NULL))
  AND (@ExcludeId IS NULL OR IDTPRMK <> @ExcludeId);";

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TypeSizeId", SqlDbType.Int).Value = item.TypeSizeId;
                command.Parameters.Add("@VerificationTypeId", SqlDbType.Int).Value = item.VerificationTypeId;
                command.Parameters.Add("@OrganizationId", SqlDbType.Int).Value = item.OrganizationId;
                command.Parameters.Add("@PlaceId", SqlDbType.Int).Value = (object)item.PlaceId ?? DBNull.Value;
                command.Parameters.Add("@GroupId", SqlDbType.Int).Value = (object)item.GroupId ?? DBNull.Value;
                command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;

                if (command.ExecuteScalar() != null)
                {
                    throw CreateTprmkDuplicateException(null);
                }
            }
        }

        private static void EnsureTpvdklIsUnique(SqlConnection connection, TpvdklDirectoryItem item, int? excludeId)
        {
            const string sql = @"
SELECT TOP (1) IDTPVDKL
FROM dbo.TPVDKL
WHERE IDTIPS = @TipsId
  AND IDSPVDKL = @StampKindId
  AND IDSPVDMK = @VerificationTypeId
  AND (@ExcludeId IS NULL OR IDTPVDKL <> @ExcludeId);";

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@TipsId", SqlDbType.Int).Value = item.TipsId;
                command.Parameters.Add("@StampKindId", SqlDbType.Int).Value = item.StampKindId;
                command.Parameters.Add("@VerificationTypeId", SqlDbType.Int).Value = item.VerificationTypeId;
                command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;

                if (command.ExecuteScalar() != null)
                {
                    throw CreateTpvdklDuplicateException(null);
                }
            }
        }

        private static InvalidOperationException CreateTipsDuplicateException(string typeName, Exception innerException)
        {
            return new InvalidOperationException(
                string.Format("РўРёРї РЎР \"{0}\" СѓР¶Рµ СЃСѓС‰РµСЃС‚РІСѓРµС‚ РґР»СЏ РІС‹Р±СЂР°РЅРЅРѕРіРѕ РЅР°РёРјРµРЅРѕРІР°РЅРёСЏ С‚РёРїР° РЎР.", typeName),
                innerException);
        }

        private static InvalidOperationException CreateTprzDuplicateException(TprzDirectoryItem item, Exception innerException)
        {
            return new InvalidOperationException(
                string.Format("РўРёРїРѕСЂР°Р·РјРµСЂ СЃ РґРёР°РїР°Р·РѕРЅРѕРј \"{0}\" Рё С…Р°СЂР°РєС‚РµСЂРёСЃС‚РёРєРѕР№ С‚РѕС‡РЅРѕСЃС‚Рё \"{1}\" СѓР¶Рµ СЃСѓС‰РµСЃС‚РІСѓРµС‚ РґР»СЏ РІС‹Р±СЂР°РЅРЅРѕРіРѕ С‚РёРїР° РЎР.", item.RangeText, item.AccuracyText),
                innerException);
        }

        private static InvalidOperationException CreateTprmcpDuplicateException(Exception innerException)
        {
            return new InvalidOperationException(
                "РўР°РєРѕР№ С†РёРєР» Рё РїРµСЂРёРѕРґ РњРљ СѓР¶Рµ СЃСѓС‰РµСЃС‚РІСѓСЋС‚ РґР»СЏ РІС‹Р±СЂР°РЅРЅРѕРіРѕ С‚РёРїРѕСЂР°Р·РјРµСЂР°.",
                innerException);
        }

        private static InvalidOperationException CreateTprmcpCycleRuleException(Exception innerException)
        {
            return new InvalidOperationException(
                "РЎРЅР°С‡Р°Р»Р° Р·Р°РїРѕР»РЅРёС‚Рµ С‚Р°Р±Р»РёС†Сѓ TPRMK РґР»СЏ РІС‹Р±СЂР°РЅРЅРѕРіРѕ С‚РёРїРѕСЂР°Р·РјРµСЂР° Рё РіСЂСѓРїРїС‹ РЎР, Р·Р°С‚РµРј РґРѕР±Р°РІСЊС‚Рµ С†РёРєР» Рё РїРµСЂРёРѕРґ РІ TPRMCP. Р‘Р°Р·Р° РґР°РЅРЅС‹С… РѕС‚РєР»РѕРЅРёР»Р° Р·Р°РїРёСЃСЊ TPRMCP, РїРѕС‚РѕРјСѓ С‡С‚Рѕ РІС‹Р±СЂР°РЅРЅС‹Р№ С†РёРєР» РњРљ С‚СЂРµР±СѓРµС‚ РІРёРґС‹ РњРљ, РєРѕС‚РѕСЂС‹С… РµС‰С‘ РЅРµС‚ РІ TPRMK.",
                innerException);
        }

        private static InvalidOperationException CreateTprmkDuplicateException(Exception innerException)
        {
            return new InvalidOperationException(
                "РўР°РєРѕР№ СЂРµРіР»Р°РјРµРЅС‚ РњРљ СѓР¶Рµ СЃСѓС‰РµСЃС‚РІСѓРµС‚ РґР»СЏ РІС‹Р±СЂР°РЅРЅРѕРіРѕ С‚РёРїРѕСЂР°Р·РјРµСЂР°.",
                innerException);
        }

        private static InvalidOperationException CreateTpvdklDuplicateException(Exception innerException)
        {
            return new InvalidOperationException(
                "РўР°РєР°СЏ РєРѕРјР±РёРЅР°С†РёСЏ РІРёРґР° РњРљ Рё РІРёРґР° РєР»РµР№РјР° СѓР¶Рµ СЃСѓС‰РµСЃС‚РІСѓРµС‚ РґР»СЏ РІС‹Р±СЂР°РЅРЅРѕРіРѕ С‚РёРїР° РЎР.",
                innerException);
        }

        private static bool IsTipsDuplicateViolation(SqlException ex)
        {
            return IsDuplicateViolation(ex, "XAK2TIPS");
        }

        private static bool IsTprzDuplicateViolation(SqlException ex)
        {
            return IsDuplicateViolation(ex, "XAK1TPRZ");
        }

        private static bool IsTprmcpDuplicateViolation(SqlException ex)
        {
            return IsDuplicateViolation(ex, "XAK1TPRMCP");
        }

        private static bool IsTprmcpCycleRuleViolation(SqlException ex)
        {
            return ex != null
                   && ex.Message.IndexOf("Р’С‹Р±СЂР°РЅРЅС‹Р№ С†РёРєР» РњРљ РЅРµ СЃРѕРѕС‚РІРµС‚СЃС‚РІСѓРµС‚ СѓРєР°Р·Р°РЅРЅС‹Рј РІРёРґР°Рј РњРљ.", StringComparison.OrdinalIgnoreCase) >= 0;
        }

        private static bool IsTprmkDuplicateViolation(SqlException ex)
        {
            return IsDuplicateViolation(ex, "XAK1TPRMK");
        }

        private static bool IsTpvdklDuplicateViolation(SqlException ex)
        {
            return IsDuplicateViolation(ex, "XAK1TPVDKL");
        }

        private static List<DeleteBlockerInfo> LoadTipsDeleteBlockers(SqlConnection connection, int id)
        {
            return LoadDeleteBlockers(connection, id, @"
SELECT blocker.TableName, blocker.LinkCount
FROM
(
    SELECT N'TPDHE' AS TableName, COUNT(*) AS LinkCount FROM dbo.TPDHE WHERE IDTIPS = @Id
    UNION ALL
    SELECT N'TPFRIZ', COUNT(*) FROM dbo.TPFRIZ WHERE IDTIPS = @Id
    UNION ALL
    SELECT N'TPND', COUNT(*) FROM dbo.TPND WHERE IDTIPS = @Id
    UNION ALL
    SELECT N'TPRZ', COUNT(*) FROM dbo.TPRZ WHERE IDTIPS = @Id
    UNION ALL
    SELECT N'TPSO', COUNT(*) FROM dbo.TPSO WHERE IDTIPS = @Id
    UNION ALL
    SELECT N'TPVDKL', COUNT(*) FROM dbo.TPVDKL WHERE IDTIPS = @Id
) blocker
WHERE blocker.LinkCount > 0
ORDER BY blocker.TableName;");
        }

        private static List<DeleteBlockerInfo> LoadTprzDeleteBlockers(SqlConnection connection, int id)
        {
            return LoadDeleteBlockers(connection, id, @"
SELECT blocker.TableName, blocker.LinkCount
FROM
(
    SELECT N'EKZ' AS TableName, COUNT(*) AS LinkCount FROM dbo.EKZ WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'GRTPRSI', COUNT(*) FROM dbo.GRTPRSI WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'KSPELTPR', COUNT(*) FROM dbo.KSPELTPR WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRDH', COUNT(*) FROM dbo.TPRDH WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRDM', COUNT(*) FROM dbo.TPRDM WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRFCTVL', COUNT(*) FROM dbo.TPRFCTVL WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRMCP', COUNT(*) FROM dbo.TPRMCP WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRMK', COUNT(*) FROM dbo.TPRMK WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRRCP', COUNT(*) FROM dbo.TPRRCP WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRRM', COUNT(*) FROM dbo.TPRRM WHERE IDTPRZ = @Id
    UNION ALL
    SELECT N'TPRTO', COUNT(*) FROM dbo.TPRTO WHERE IDTPRZ = @Id
) blocker
WHERE blocker.LinkCount > 0
ORDER BY blocker.TableName;");
        }

        private static List<DeleteBlockerInfo> LoadTprmcpDeleteBlockers(SqlConnection connection, int id)
        {
            return LoadDeleteBlockers(connection, id, @"
SELECT blocker.TableName, blocker.LinkCount
FROM
(
    SELECT N'EKZMCP' AS TableName, COUNT(*) AS LinkCount FROM dbo.EKZMCP WHERE IDTPRMCP = @Id
) blocker
WHERE blocker.LinkCount > 0
ORDER BY blocker.TableName;");
        }

        private static List<DeleteBlockerInfo> LoadTprmkDeleteBlockers(SqlConnection connection, int id)
        {
            return LoadDeleteBlockers(connection, id, @"
SELECT blocker.TableName, blocker.LinkCount
FROM
(
    SELECT N'EKZMKF' AS TableName, COUNT(*) AS LinkCount FROM dbo.EKZMKF WHERE IDTPRMK = @Id
    UNION ALL
    SELECT N'TPRMKM', COUNT(*) FROM dbo.TPRMKM WHERE IDTPRMK = @Id
) blocker
WHERE blocker.LinkCount > 0
ORDER BY blocker.TableName;");
        }

        private static string CreateTipsDeleteBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
        {
            return CreateDeleteBlockedMessage("TIPS", blockers);
        }

        private static string CreateTprzDeleteBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
        {
            return CreateDeleteBlockedMessage("TPRZ", blockers);
        }

        private static string CreateTprmcpDeleteBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
        {
            return CreateDeleteBlockedMessage("TPRMCP", blockers);
        }

        private static string CreateTprmkDeleteBlockedMessage(IEnumerable<DeleteBlockerInfo> blockers)
        {
            return CreateDeleteBlockedMessage("TPRMK", blockers);
        }

        private static string CreateTipsDeleteBlockedMessage(SqlException ex)
        {
            return CreateDeleteBlockedMessage("TIPS", ex);
        }

        private static string CreateTprzDeleteBlockedMessage(SqlException ex)
        {
            return CreateDeleteBlockedMessage("TPRZ", ex);
        }

        private static string CreateTprmcpDeleteBlockedMessage(SqlException ex)
        {
            return CreateDeleteBlockedMessage("TPRMCP", ex);
        }

        private static string CreateTprmkDeleteBlockedMessage(SqlException ex)
        {
            return CreateDeleteBlockedMessage("TPRMK", ex);
        }

        private static SqlConnection CreateConnection()
        {
            return SqlServerConnectionFactory.Current.CreateConnection();
        }

        private static IReadOnlyList<DirectoryLookupItem> LoadLookupItems(string sql)
        {
            var items = new List<DirectoryLookupItem>();

            using (var connection = CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new DirectoryLookupItem
                        {
                            Id = GetInt32(reader, "Id"),
                            Name = GetString(reader, "Name")
                        });
                    }
                }
            }

            return items;
        }

        private static int NormalizeRequiredId(int value, string errorMessage)
        {
            if (value <= 0)
            {
                throw new InvalidOperationException(errorMessage);
            }

            return value;
        }

        private static int? NormalizeOptionalId(int? value)
        {
            return value.HasValue && value.Value > 0 ? value : (int?)null;
        }

        private static string NormalizeRequiredString(string value, string emptyErrorMessage, int maxLength, string tooLongErrorMessage)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new InvalidOperationException(emptyErrorMessage);
            }

            var normalizedValue = value.Trim();
            if (normalizedValue.Length > maxLength)
            {
                throw new InvalidOperationException(tooLongErrorMessage);
            }

            return normalizedValue;
        }

        private static string NormalizeOptionalString(string value, int maxLength, string tooLongErrorMessage)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return null;
            }

            var normalizedValue = value.Trim();
            if (normalizedValue.Length > maxLength)
            {
                throw new InvalidOperationException(tooLongErrorMessage);
            }

            return normalizedValue;
        }

        private static string NormalizeOptionalText(string value)
        {
            return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
        }

        private static void AddNullableIntParameter(SqlCommand command, string name, int? value)
        {
            command.Parameters.Add(name, SqlDbType.Int).Value = (object)value ?? DBNull.Value;
        }

        private static void AddNullableBooleanParameter(SqlCommand command, string name, bool? value)
        {
            command.Parameters.Add(name, SqlDbType.Bit).Value = (object)value ?? DBNull.Value;
        }

        private static void AddNullableStringParameter(SqlCommand command, string name, SqlDbType dbType, int size, string value)
        {
            command.Parameters.Add(name, dbType, size).Value = (object)value ?? DBNull.Value;
        }

        private static void AddNullableTextParameter(SqlCommand command, string name, string value)
        {
            command.Parameters.Add(name, SqlDbType.Text).Value = (object)value ?? DBNull.Value;
        }

        private static void AddNullableMoneyParameter(SqlCommand command, string name, decimal? value)
        {
            command.Parameters.Add(name, SqlDbType.Money).Value = (object)value ?? DBNull.Value;
        }

        private static void AddNullableDecimal62Parameter(SqlCommand command, string name, decimal? value)
        {
            var parameter = command.Parameters.Add(name, SqlDbType.Decimal);
            parameter.Precision = 6;
            parameter.Scale = 2;
            parameter.Value = (object)value ?? DBNull.Value;
        }

        private static bool IsDuplicateViolation(SqlException ex, string indexName)
        {
            return ex != null
                   && (ex.Number == 2601 || ex.Number == 2627)
                   && ex.Message.IndexOf(indexName, StringComparison.OrdinalIgnoreCase) >= 0;
        }

        private static DirectoryDeleteResult CreateSuccessDeleteResult()
        {
            return new DirectoryDeleteResult
            {
                IsDeleted = true
            };
        }

        private static DirectoryDeleteResult CreateBlockedDeleteResult(string message)
        {
            return new DirectoryDeleteResult
            {
                IsDeleted = false,
                WarningMessage = message
            };
        }

        private static string CreateDeleteBlockedMessage(string tableName, IEnumerable<DeleteBlockerInfo> blockers)
        {
            var details = string.Join(", ", (blockers ?? Enumerable.Empty<DeleteBlockerInfo>()).Select(delegate(DeleteBlockerInfo blocker)
            {
                return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount);
            }));

            return string.Format(
                "Р—Р°РїРёСЃСЊ {0} РЅРµ РјРѕР¶РµС‚ Р±С‹С‚СЊ СѓРґР°Р»РµРЅР°, РїРѕС‚РѕРјСѓ С‡С‚Рѕ РЅР° РЅРµС‘ РµСЃС‚СЊ СЃСЃС‹Р»РєРё РІ С‚Р°Р±Р»РёС†Р°С…: {1}.",
                tableName,
                string.IsNullOrWhiteSpace(details) ? "СЃРІСЏР·Р°РЅРЅС‹Рµ РґР°РЅРЅС‹Рµ" : details);
        }

        private static string CreateDeleteBlockedMessage(string tableName, SqlException ex)
        {
            return string.Format(
                "Р—Р°РїРёСЃСЊ {0} РЅРµ РјРѕР¶РµС‚ Р±С‹С‚СЊ СѓРґР°Р»РµРЅР° РёР·-Р·Р° РѕРіСЂР°РЅРёС‡РµРЅРёСЏ СЃСЃС‹Р»РѕС‡РЅРѕР№ С†РµР»РѕСЃС‚РЅРѕСЃС‚Рё РІ Р‘Р”.{1}",
                tableName,
                ex == null ? string.Empty : " " + ex.Message);
        }

        private static List<DeleteBlockerInfo> LoadDeleteBlockers(SqlConnection connection, int id, string sql)
        {
            var blockers = new List<DeleteBlockerInfo>();

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds;
                command.Parameters.Add("@Id", SqlDbType.Int).Value = id;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        blockers.Add(new DeleteBlockerInfo
                        {
                            TableName = GetString(reader, "TableName"),
                            RowCount = GetInt32(reader, "LinkCount")
                        });
                    }
                }
            }

            return blockers;
        }

        private static int GetInt32(IDataRecord record, string columnName)
        {
            return Convert.ToInt32(record[columnName]);
        }

        private static string GetString(IDataRecord record, string columnName)
        {
            return record[columnName] == DBNull.Value ? string.Empty : Convert.ToString(record[columnName]);
        }

        private static int? GetNullableInt32(IDataRecord record, string columnName)
        {
            return record[columnName] == DBNull.Value ? (int?)null : Convert.ToInt32(record[columnName]);
        }

        private static bool? GetNullableBoolean(IDataRecord record, string columnName)
        {
            return record[columnName] == DBNull.Value ? (bool?)null : Convert.ToBoolean(record[columnName]);
        }

        private static decimal? GetNullableDecimal(IDataRecord record, string columnName)
        {
            return record[columnName] == DBNull.Value ? (decimal?)null : Convert.ToDecimal(record[columnName]);
        }

        private sealed class DeleteBlockerInfo
        {
            public int RowCount { get; set; }

            public string TableName { get; set; }
        }
    }
}

