// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
// Created:    2009.06.23

using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using Xtensive.Sql.Info;

namespace Xtensive.Sql.Drivers.SqlServer
{
  internal abstract class Driver : SqlDriver
  {
    private readonly ErrorMessageParser errorMessageParser;
    private readonly bool checkConnectionIsAlive;

    protected override SqlConnection DoCreateConnection()
    {
      return new Connection(this, checkConnectionIsAlive);
    }

    public override SqlExceptionType GetExceptionType(Exception exception)
    {
      return GetExceptionInfo(exception).Type;
    }

    public override SqlExceptionInfo GetExceptionInfo(Exception exception)
    {
      var nativeException = exception as SqlException;
      if (nativeException==null)
        return SqlExceptionInfo.Create(SqlExceptionType.Unknown);
      int errorCode = nativeException.Number;
      string errorMessage = nativeException.Message;
      if (errorCode==-2)
        return SqlExceptionInfo.Create(SqlExceptionType.OperationTimeout);
      if (errorCode==1205)
        return SqlExceptionInfo.Create(SqlExceptionType.Deadlock);
      if ((errorCode >= 3950 && errorCode <= 3961) || errorCode==3966 || errorCode==3971)
        return SqlExceptionInfo.Create(SqlExceptionType.SerializationFailure);
      if (errorCode==229 || errorCode==230)
        return SqlExceptionInfo.Create(SqlExceptionType.Unknown);
      if (errorCode >= 100 && errorCode <= 499)
        return SqlExceptionInfo.Create(SqlExceptionType.SyntaxError);
      var info = new SqlExceptionInfo();
      if (TryProvideErrorContext(errorCode, errorMessage, info)) {
        info.Lock();
        return info;
      }
      return SqlExceptionInfo.Create(SqlExceptionType.Unknown);
    }

    protected virtual bool TryProvideErrorContext(int errorCode, string errorMessage, SqlExceptionInfo info)
    {
      Dictionary<int, string> parseResult;
      string duplicateValue;
      switch (errorCode) {
        case 2627:
          parseResult = errorMessageParser.Parse(errorCode, errorMessage);
          info.Type = SqlExceptionType.UniqueConstraintViolation;
          info.Constraint = parseResult[2];
          info.Table = ErrorMessageParser.CutSchemaPrefix(parseResult[3]);
          if (parseResult.TryGetValue(4, out duplicateValue))
            info.Value = duplicateValue;
          break;
        case 2601:
          parseResult = errorMessageParser.Parse(errorCode, errorMessage);
          info.Type = SqlExceptionType.UniqueConstraintViolation;
          info.Table = ErrorMessageParser.CutSchemaPrefix(parseResult[1]);
          info.Constraint = parseResult[2];
          if (parseResult.TryGetValue(3, out duplicateValue))
            info.Value = duplicateValue;
          break;
        case 515:
          parseResult = errorMessageParser.Parse(errorCode, errorMessage);
          info.Type = SqlExceptionType.CheckConstraintViolation;
          info.Column = parseResult[1];
          info.Table = ErrorMessageParser.CutDatabaseAndSchemaPrefix(parseResult[2]);
          break;
        case 547:
          parseResult = errorMessageParser.Parse(errorCode, errorMessage);
          switch (parseResult[2]) {
            case "FOREIGN KEY":
            case "REFERENCE":
              info.Type = SqlExceptionType.ReferentialConstraintViolation;
              break;
            case "UNIQUE KEY":
            case "PRIMARY KEY":
              info.Type = SqlExceptionType.UniqueConstraintViolation;
              break;
            case "CHECK":
              info.Type = SqlExceptionType.CheckConstraintViolation;
              break;
            default:
              return false;
          }
          info.Constraint = parseResult[3];
          info.Database = parseResult[4];
          info.Table = ErrorMessageParser.CutSchemaPrefix(parseResult[5]);
          info.Column = ErrorMessageParser.ExtractQuotedText(parseResult[6]);
          break;
        default:
          return false;
      }
      return true;
    }

    // Constructors

    protected Driver(CoreServerInfo coreServerInfo, ErrorMessageParser errorMessageParser, bool checkConnectionIsAlive)
      : base(coreServerInfo)
    {
      this.errorMessageParser = errorMessageParser;
      this.checkConnectionIsAlive = checkConnectionIsAlive;
    }
  }
}