using System.Linq;
using System.Runtime.Serialization;
using NUnit.Framework;
using ServiceStack.DataAnnotations;

namespace ServiceStack.OrmLite.MySql.Tests
{
    public class EnumTests : OrmLiteTestBase
    {
        [Test]
        public void CanCreateTable()
        {
            OpenDbConnection().CreateTable<TypeWithEnum>(true);
        }

        [Test]
        public void CanStoreEnumValue()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithEnum>(true);
                con.Save(new TypeWithEnum { Id = 1, EnumValue = SomeEnum.Value1 });
            }
        }

        [Test]
        public void CanGetEnumValue()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithEnum>(true);
                var obj = new TypeWithEnum { Id = 1, EnumValue = SomeEnum.Value1 };
                con.Save(obj);
                var target = con.SingleById<TypeWithEnum>(obj.Id);
                Assert.AreEqual(obj.Id, target.Id);
                Assert.AreEqual(obj.EnumValue, target.EnumValue);
            }
        }

        [Test]
        public void CanCreateTableNullableEnum()
        {
            OpenDbConnection().CreateTable<TypeWithNullableEnum>(true);
        }

        [Test]
        public void CanStoreNullableEnumValue()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithNullableEnum>(true);
                con.Save(new TypeWithNullableEnum { Id = 1, EnumValue = SomeEnum.Value1 });
            }
        }

        [Test]
        public void CanGetNullableEnumValue()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithNullableEnum>(true);
                var obj = new TypeWithNullableEnum { Id = 1, EnumValue = SomeEnum.Value1 };
                con.Save(obj);
                var target = con.SingleById<TypeWithNullableEnum>(obj.Id);
                Assert.AreEqual(obj.Id, target.Id);
                Assert.AreEqual(obj.EnumValue, target.EnumValue);
            }
        }

        [Test]
        public void CanQueryByEnumValue_using_select_with_expression()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithEnum>(true);
                con.Save(new TypeWithEnum { Id = 1, EnumValue = SomeEnum.Value1 });
                con.Save(new TypeWithEnum { Id = 2, EnumValue = SomeEnum.Value1 });
                con.Save(new TypeWithEnum { Id = 3, EnumValue = SomeEnum.Value2 });

                var target = con.Select<TypeWithEnum>(q => q.EnumValue == SomeEnum.Value1);

                Assert.AreEqual(2, target.Count());
            }
        }

        [Test]
        public void CanQueryByEnumValue_using_select_with_string()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithEnum>(true);
                con.Save(new TypeWithEnum { Id = 1, EnumValue = SomeEnum.Value1 });
                con.Save(new TypeWithEnum { Id = 2, EnumValue = SomeEnum.Value1 });
                con.Save(new TypeWithEnum { Id = 3, EnumValue = SomeEnum.Value2 });

                var target = con.Select<TypeWithEnum>("EnumValue = @value", new { value = SomeEnum.Value1 });

                Assert.AreEqual(2, target.Count());
            }
        }

        [Test]
        public void CanQueryByEnumValue_using_where_with_AnonType()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithEnum>(true);
                con.Save(new TypeWithEnum { Id = 1, EnumValue = SomeEnum.Value1 });
                con.Save(new TypeWithEnum { Id = 2, EnumValue = SomeEnum.Value1 });
                con.Save(new TypeWithEnum { Id = 3, EnumValue = SomeEnum.Value2 });

                var target = con.Where<TypeWithEnum>(new { EnumValue = SomeEnum.Value1 });

                Assert.AreEqual(2, target.Count());
            }
        }

        [Test]
        public void CanQueryByNullableEnumValue_using_where_with_AnonType()
        {
            using (var con = OpenDbConnection())
            {
                con.CreateTable<TypeWithEnum>(true);
                con.Save(new TypeWithNullableEnum { Id = 1, EnumValue = SomeEnum.Value1, IntEnum = SomeIntEnum.One });
                con.Save(new TypeWithNullableEnum { Id = 2, EnumValue = SomeEnum.Value1, IntEnum = SomeIntEnum.One });
                con.Save(new TypeWithNullableEnum { Id = 3, EnumValue = SomeEnum.Value2, IntEnum = SomeIntEnum.Two });

                var target = con.Where<TypeWithNullableEnum>(new { EnumValue = SomeEnum.Value1 });
                var enumInt = con.Where<TypeWithNullableEnum>(new { IntEnum = SomeIntEnum.One });

                Assert.AreEqual(2, target.Count);
                Assert.AreEqual(2, enumInt.Count);
            }
        }

        [Test]
        public void CanSaveNullableEnum_with_specific_id_select_with_anon_type()
        {
            using (var db = OpenDbConnection())
            {
                db.DropAndCreateTable<MyObj>();
                var myObj = new MyObj();
                myObj.Id = 1;
                myObj.Test = MyEnum.One;
                db.Insert(myObj);

                myObj = db.Single<MyObj>(new { Id = 1 });

                Assert.That(myObj.Id, Is.EqualTo(1));
                Assert.That(myObj.Test, Is.Not.EqualTo(null));
                Assert.That(myObj.Test, Is.EqualTo(MyEnum.One));
            }
        }

        [Test]
        public void CanSaveNullableEnum_with_specific_id_select_with_type()
        {
            using (var db = OpenDbConnection())
            {
                db.DropAndCreateTable<MyObj>();
                var exists = db.SingleById<MyObj>(1);
                if (exists != null)
                {
                    db.DeleteById<MyObj>(1);
                }
            }

            using (var con = OpenDbConnection())
            {

                var myObj = new MyObj();
                myObj.Id = 1;
                myObj.Test = MyEnum.One;
                con.Insert(myObj);

                myObj = con.Single<MyObj>(x => x.Id == 1);

                Assert.That(myObj.Id, Is.EqualTo(1));
                Assert.That(myObj.Test, Is.Not.EqualTo(null));
                Assert.That(myObj.Test, Is.EqualTo(MyEnum.One));
            }
        }

        [Test]
        public void ShouldUseEnumName_in_Sql_TableAlias()
        {
            using var db = OpenDbConnection();
            var q = db.From<EntityMonitor>();
            var stateType = StateType.完好;
            q.Or<ChnStateResult>((x) => Sql.TableAlias(x.State, nameof(LinkResult)) == StateType.离线);
            q.Or<ChnStateResult>((x) => x.State == StateType.离线);
            q.Or<ChnStateResult>((x) => Sql.TableAlias(x.State, nameof(LinkResult)) == stateType);
            q.And<ChnStateResult>((x) => Sql.TableAlias(x.Age, nameof(LinkResult)) > 18);
            q.And<ChnStateResult>((x) => Sql.TableAlias(x.SomeIntEnum, nameof(LinkResult)) == SomeIntEnum.One);
            q.And<ChnStateResult>((x) => x.SomeIntEnum== SomeIntEnum.One);

            var sql = q.ToMergedParamsSelectStatement();
            Assert.That(q.Params[0].Value, Is.EqualTo(nameof(StateType.离线))); 
            Assert.That(q.Params[1].Value, Is.EqualTo(nameof(StateType.离线))); 
            Assert.That(q.Params[2].Value, Is.EqualTo(nameof(StateType.完好))); 
            Assert.That(q.Params[3].Value, Is.EqualTo(18)); 
            Assert.That(q.Params[4].Value, Is.EqualTo((int)SomeIntEnum.One)); 
            Assert.That(q.Params[5].Value, Is.EqualTo((int)SomeIntEnum.One)); 
        }
    }

    public class EntityMonitor
    {
        [Alias("文1")]
        public StateType State { get; set; }

        public int Age { get; set; }

        public SomeIntEnum SomeIntEnum { get; set; }
    }

    public class LinkResult : EntityMonitor
    {

    }

    public class ChnStateResult : EntityMonitor
    {

    }

    public enum StateType
    {
        完好,
        离线
    }

    public enum SomeEnum : long
    {
        Value1 = 2147483648,
        Value2,
        Value3
    }

    [EnumAsInt]
    public enum SomeIntEnum
    {
        Zero = 0,
        One = 1,
        Two = 2
    }

    [EnumAsInt]
    public enum MyEnum : int
    {
        Zero = 0,
        One = 1
    }

    public class MyObj
    {
        public int Id { get; set; }
        public MyEnum? Test { get; set; }
    }

    public class TypeWithEnum
    {
        public int Id { get; set; }
        public SomeEnum EnumValue { get; set; }
        public SomeIntEnum IntEnum { get; set; }
    }

    public class TypeWithNullableEnum
    {
        public int Id { get; set; }
        public SomeEnum? EnumValue { get; set; }
        public SomeIntEnum? IntEnum { get; set; }
    }
}
