using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace XLAB
{
    internal sealed class PlanningService
    {
        public int AddPlanItem(PlanningEditResult item)
        {
            if (item == null)
            {
                throw new InvalidOperationException("Не переданы данные плановой записи.");
            }

            const string sql = @"
INSERT INTO dbo.EKZMCP
(
    IDEKZ,
    IDTPRMCP,
    DTMKPLO,
    PZMCO,
    IDSPVDMK
)
VALUES
(
    @InstrumentId,
    @TemplateId,
    @PlannedOn,
    NULL,
    NULL
);

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

            using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsurePlanIsUnique(connection, item.InstrumentId, item.TemplateId, null);

                command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
                command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = item.InstrumentId;
                command.Parameters.Add("@TemplateId", SqlDbType.Int).Value = item.TemplateId;
                command.Parameters.Add("@PlannedOn", SqlDbType.DateTime).Value = item.PlannedOn.Date;

                try
                {
                    return Convert.ToInt32(command.ExecuteScalar());
                }
                catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "XAK1EKZMCP"))
                {
                    throw CreatePlanDuplicateException(ex);
                }
            }
        }

        public void DeletePlanItem(int id)
        {
            if (id <= 0)
            {
                throw new InvalidOperationException("Не выбрана запись EKZMCP для удаления.");
            }

            const string sql = @"
DELETE FROM dbo.EKZMCP
WHERE IDEKZMCP = @Id;

SELECT @@ROWCOUNT;";

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

                if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                {
                    throw new InvalidOperationException("Запись EKZMCP для удаления не найдена.");
                }
            }
        }

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

        public IReadOnlyList<PlanningItem> LoadPlanItems(int year)
        {
            if (year < 2000 || year > 2100)
            {
                throw new InvalidOperationException("Год планирования должен быть в диапазоне 2000-2100.");
            }

            const string sql = @"
SELECT
    z.IDEKZ AS InstrumentId,
    z.IDTPRZ AS TypeSizeId,
    z.IDFRPDV AS OwnerOrganizationId,
    ownerOrg.NMFRPD AS OwnerOrganizationName,
    areas.NMOI AS MeasurementAreaName,
    names.NMTP AS InstrumentName,
    tips.TP AS TypeName,
    tprz.DPZN AS RangeText,
    CONVERT(nvarchar(50), tprz.NNGSRS) AS RegistryNumber,
    z.NNZV AS SerialNumber,
    z.NNIN AS InventoryNumber,
    lastMk.LastVerificationOn,
    selectedPlan.IDEKZMCP AS PlanId,
    CASE
        WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN selectedPlan.IDTPRMCP
        ELSE COALESCE(periodByInstrument.IDTPRMCP, periodByType.IDTPRMCP)
    END AS EffectiveTemplateId,
    CASE
        WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN selectedPlan.PRMK
        ELSE COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR)
    END AS PeriodMonths,
    CASE
        WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN selectedPlan.DTMKPLO
        ELSE DATEADD(month, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR), lastMk.LastVerificationOn)
    END AS PlannedOn,
    CASE
        WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN CAST(1 AS bit)
        ELSE CAST(0 AS bit)
    END AS IsExplicitPlan,
    CASE
        WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN N'План из EKZMCP'
        WHEN periodByInstrument.IDTPRMCP IS NOT NULL THEN N'Расчет по TPRMCP экземпляра'
        WHEN periodByType.IDTPRMCP IS NOT NULL THEN N'Расчет по TPRMCP типоразмера'
        WHEN tips.PRMKGR IS NOT NULL THEN N'Расчет по TIPS.PRMKGR'
        ELSE N''
    END AS PlanSource,
    CASE
        WHEN selectedPlan.IDEKZMCP IS NOT NULL THEN N'EKZMCP / TPRMCP'
        WHEN periodByInstrument.IDTPRMCP IS NOT NULL THEN N'TPRMCP экземпляра'
        WHEN periodByType.IDTPRMCP IS NOT NULL THEN N'TPRMCP типоразмера'
        WHEN tips.PRMKGR IS NOT NULL THEN N'TIPS.PRMKGR'
        ELSE N''
    END AS PeriodSource
FROM dbo.EKZ z
JOIN dbo.TPRZ tprz ON tprz.IDTPRZ = z.IDTPRZ
JOIN dbo.TIPS tips ON tips.IDTIPS = tprz.IDTIPS
JOIN dbo.SPNMTP names ON names.IDSPNMTP = tips.IDSPNMTP
JOIN dbo.SPOI areas ON areas.IDSPOI = tips.IDSPOI
JOIN dbo.FRPD ownerOrg ON ownerOrg.IDFRPD = z.IDFRPDV
OUTER APPLY
(
    SELECT TOP (1)
        COALESCE(m.DTMKFK, m.DTVDM, m.DTPRM) AS LastVerificationOn
    FROM dbo.EKZMK m
    WHERE m.IDEKZ = z.IDEKZ
      AND COALESCE(m.DTMKFK, m.DTVDM, m.DTPRM) IS NOT NULL
    ORDER BY COALESCE(m.DTMKFK, m.DTVDM, m.DTPRM) DESC, m.IDEKZMK DESC
) lastMk
OUTER APPLY
(
    SELECT TOP (1)
        e.IDEKZMCP,
        e.IDTPRMCP,
        e.DTMKPLO,
        t.PRMK
    FROM dbo.EKZMCP e
    JOIN dbo.TPRMCP t ON t.IDTPRMCP = e.IDTPRMCP
    WHERE e.IDEKZ = z.IDEKZ
      AND e.DTMKPLO >= @YearStart
      AND e.DTMKPLO < @YearEnd
    ORDER BY e.DTMKPLO, e.IDEKZMCP DESC
) selectedPlan
OUTER APPLY
(
    SELECT TOP (1)
        e.IDEKZMCP AS PlanId
    FROM dbo.EKZMCP e
    WHERE e.IDEKZ = z.IDEKZ
      AND e.DTMKPLO >= @YearEnd
    ORDER BY e.DTMKPLO, e.IDEKZMCP DESC
) futurePlan
OUTER APPLY
(
    SELECT TOP (1)
        e.IDTPRMCP,
        t.PRMK
    FROM dbo.EKZMCP e
    JOIN dbo.TPRMCP t ON t.IDTPRMCP = e.IDTPRMCP
    WHERE e.IDEKZ = z.IDEKZ
    ORDER BY e.IDEKZMCP DESC, t.IDTPRMCP DESC
) periodByInstrument
OUTER APPLY
(
    SELECT TOP (1)
        t.IDTPRMCP,
        t.PRMK
    FROM dbo.TPRMCP t
    WHERE t.IDTPRZ = z.IDTPRZ
    ORDER BY t.IDTPRMCP DESC
) periodByType
WHERE ISNULL(z.IsDeleted, 0) = 0
  AND
  (
      selectedPlan.IDEKZMCP IS NOT NULL
      OR
      (
          futurePlan.PlanId IS NULL
          AND lastMk.LastVerificationOn IS NOT NULL
          AND COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR) IS NOT NULL
          AND DATEADD(month, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR), lastMk.LastVerificationOn) >= @YearStart
          AND DATEADD(month, COALESCE(periodByInstrument.PRMK, periodByType.PRMK, tips.PRMKGR), lastMk.LastVerificationOn) < @YearEnd
      )
  )
ORDER BY ownerOrg.NMFRPD, areas.NMOI, names.NMTP, tips.TP, tprz.DPZN, z.NNZV, z.IDEKZ;";

            var yearStart = new DateTime(year, 1, 1);
            var yearEnd = yearStart.AddYears(1);
            var items = new List<PlanningItem>();

            using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
                command.Parameters.Add("@YearStart", SqlDbType.DateTime).Value = yearStart;
                command.Parameters.Add("@YearEnd", SqlDbType.DateTime).Value = yearEnd;

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new PlanningItem
                        {
                            InstrumentId = ReferenceDirectorySqlHelpers.GetInt32(reader, "InstrumentId"),
                            TypeSizeId = ReferenceDirectorySqlHelpers.GetInt32(reader, "TypeSizeId"),
                            OwnerOrganizationId = ReferenceDirectorySqlHelpers.GetInt32(reader, "OwnerOrganizationId"),
                            OwnerOrganizationName = ReferenceDirectorySqlHelpers.GetString(reader, "OwnerOrganizationName"),
                            MeasurementAreaName = ReferenceDirectorySqlHelpers.GetString(reader, "MeasurementAreaName"),
                            InstrumentName = ReferenceDirectorySqlHelpers.GetString(reader, "InstrumentName"),
                            TypeName = ReferenceDirectorySqlHelpers.GetString(reader, "TypeName"),
                            RangeText = ReferenceDirectorySqlHelpers.GetString(reader, "RangeText"),
                            RegistryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "RegistryNumber"),
                            SerialNumber = ReferenceDirectorySqlHelpers.GetString(reader, "SerialNumber"),
                            InventoryNumber = ReferenceDirectorySqlHelpers.GetString(reader, "InventoryNumber"),
                            LastVerificationOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "LastVerificationOn"),
                            PlanId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "PlanId"),
                            EffectiveTemplateId = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "EffectiveTemplateId"),
                            PeriodMonths = ReferenceDirectorySqlHelpers.GetNullableInt32(reader, "PeriodMonths"),
                            PlannedOn = ReferenceDirectorySqlHelpers.GetNullableDateTime(reader, "PlannedOn"),
                            IsExplicitPlan = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsExplicitPlan"))),
                            PlanSource = ReferenceDirectorySqlHelpers.GetString(reader, "PlanSource"),
                            PeriodSource = ReferenceDirectorySqlHelpers.GetString(reader, "PeriodSource")
                        });
                    }
                }
            }

            return items;
        }

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

            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<PlanningTemplateOption>();

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

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

            return items;
        }

        public void UpdatePlanItem(PlanningEditResult item)
        {
            if (item == null || !item.PlanId.HasValue || item.PlanId.Value <= 0)
            {
                throw new InvalidOperationException("Не выбрана запись EKZMCP для изменения.");
            }

            const string sql = @"
UPDATE dbo.EKZMCP
SET IDEKZ = @InstrumentId,
    IDTPRMCP = @TemplateId,
    DTMKPLO = @PlannedOn
WHERE IDEKZMCP = @Id;

SELECT @@ROWCOUNT;";

            using (var connection = ReferenceDirectorySqlHelpers.CreateConnection())
            using (var command = new SqlCommand(sql, connection))
            {
                connection.Open();
                EnsurePlanIsUnique(connection, item.InstrumentId, item.TemplateId, item.PlanId.Value);

                command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
                command.Parameters.Add("@Id", SqlDbType.Int).Value = item.PlanId.Value;
                command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = item.InstrumentId;
                command.Parameters.Add("@TemplateId", SqlDbType.Int).Value = item.TemplateId;
                command.Parameters.Add("@PlannedOn", SqlDbType.DateTime).Value = item.PlannedOn.Date;

                try
                {
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        throw new InvalidOperationException("Запись EKZMCP для изменения не найдена.");
                    }
                }
                catch (SqlException ex) when (ReferenceDirectorySqlHelpers.IsDuplicateViolation(ex, "XAK1EKZMCP"))
                {
                    throw CreatePlanDuplicateException(ex);
                }
            }
        }

        private static Exception CreatePlanDuplicateException(SqlException ex)
        {
            var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message)
                ? string.Empty
                : " " + ex.Message.Trim();

            return new InvalidOperationException("Для выбранного прибора и периода TPRMCP запись EKZMCP уже существует." + suffix, ex);
        }

        private static void EnsurePlanIsUnique(SqlConnection connection, int instrumentId, int templateId, int? excludeId)
        {
            const string sql = @"
SELECT TOP (1) IDEKZMCP
FROM dbo.EKZMCP
WHERE IDEKZ = @InstrumentId
  AND IDTPRMCP = @TemplateId
  AND (@ExcludeId IS NULL OR IDEKZMCP <> @ExcludeId);";

            using (var command = new SqlCommand(sql, connection))
            {
                command.CommandTimeout = ReferenceDirectorySqlHelpers.GetCommandTimeoutSeconds();
                command.Parameters.Add("@InstrumentId", SqlDbType.Int).Value = instrumentId;
                command.Parameters.Add("@TemplateId", SqlDbType.Int).Value = templateId;
                command.Parameters.Add("@ExcludeId", SqlDbType.Int).Value = (object)excludeId ?? DBNull.Value;

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