// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Migrations
{
    using System.CodeDom.Compiler;
    using System.Data.Entity.Migrations.Design;
    using System.Data.Entity.SqlServer;
    using System.IO;
    using System.Linq;
    using System.Text;
    using Microsoft.CSharp;
    using Xunit;

    [Variant(DatabaseProvider.SqlClient, ProgrammingLanguage.CSharp)]
    public class ToolingScenarios : DbTestCase, IUseFixture<ToolingFixture>
    {
        private string _projectDir;

        [MigrationsTheory]
        public void Can_update()
        {
            ResetDatabase();

            var logBuilder = new StringBuilder();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                facade.LogInfoDelegate = m => logBuilder.AppendLine("INFO: " + m);
                facade.LogVerboseDelegate = s => logBuilder.AppendLine("SQL: " + s);

                facade.Update(null, false);
            }

            var log = logBuilder.ToString();
            Assert.True(log.Contains("INFO: Applying automatic migration"));
            Assert.True(log.Contains("SQL: CREATE TABLE [dbo].[Entities]"));

            Assert.True(DatabaseExists());
            Assert.True(TableExists("Entities"));
        }

        [MigrationsTheory]
        public void Can_script_update()
        {
            ResetDatabase();

            var logBuilder = new StringBuilder();
            string sql;

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                facade.LogInfoDelegate = m => logBuilder.AppendLine("INFO: " + m);

                sql = facade.ScriptUpdate(null, null, false);
            }

            Assert.True(sql.Contains("CREATE TABLE [dbo].[Entities]"));

            var log = logBuilder.ToString();
            Assert.True(log.Contains("INFO: Applying automatic migration"));
        }

        [MigrationsTheory]
        public void Can_get_context_types()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                configurationTypeName: null,
                workingDirectory: _projectDir,
                configurationFilePath: null,
                dataDirectory: null,
                connectionStringInfo: null))
            {
                var result = facade.GetContextTypes();

                Assert.Equal(2, result.Count());
                Assert.Equal("ClassLibrary1.Context", result.First());
            }
        }

        [MigrationsTheory]
        public void Can_get_context_type()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                configurationTypeName: null,
                workingDirectory: _projectDir,
                configurationFilePath: null,
                dataDirectory: null,
                connectionStringInfo: null))
            {
                var result = facade.GetContextType(null);

                Assert.Equal("ClassLibrary1.Context", result);
            }
        }

        [MigrationsTheory]
        public void Can_get_context_type_by_name()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                configurationTypeName: null,
                workingDirectory: _projectDir,
                configurationFilePath: null,
                dataDirectory: null,
                connectionStringInfo: null))
            {
                var result = facade.GetContextType("Context");

                Assert.Equal("ClassLibrary1.Context", result);
            }
        }

        [MigrationsTheory]
        public void Can_get_context_type_by_qualified_name()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                configurationTypeName: null,
                workingDirectory: _projectDir,
                configurationFilePath: null,
                dataDirectory: null,
                connectionStringInfo: null))
            {
                var result = facade.GetContextType("ClassLibrary1.Context");

                Assert.Equal("ClassLibrary1.Context", result);
            }
        }

        [MigrationsTheory]
        public void Throws_when_context_type_not_found()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                configurationTypeName: null,
                workingDirectory: _projectDir,
                configurationFilePath: null,
                dataDirectory: null,
                connectionStringInfo: null))
            {
                Assert.Throws<ToolingException>(() => facade.GetContextType("MissingContext"));
            }
        }

        [MigrationsTheory]
        public void Can_scaffold_initial_create()
        {
            ResetDatabase();

            var migrator = CreateMigrator<ShopContext_v1>();

            var initialCreate = new MigrationScaffolder(migrator.Configuration).Scaffold("InitialCreate");

            migrator = CreateMigrator<ShopContext_v1>(scaffoldedMigrations: initialCreate, contextKey: "ClassLibrary1.Context");

            migrator.Update();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                var scaffoldedMigration = facade.ScaffoldInitialCreate("cs", "ClassLibrary1");

                Assert.True(scaffoldedMigration.DesignerCode.Length > 500);
                Assert.Equal("cs", scaffoldedMigration.Language);
                Assert.True(scaffoldedMigration.MigrationId.EndsWith("_InitialCreate"));
                Assert.True(scaffoldedMigration.UserCode.Length > 500);
            }
        }

        [MigrationsTheory]
        public void Can_scaffold_empty()
        {
            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                var scaffoldedMigration = facade.Scaffold("Create", "cs", "ClassLibrary1", ignoreChanges: true);

                Assert.True(scaffoldedMigration.DesignerCode.Length > 500);
                Assert.Equal("cs", scaffoldedMigration.Language);
                Assert.True(scaffoldedMigration.MigrationId.EndsWith("_Create"));
                Assert.True(scaffoldedMigration.UserCode.Length < 300);
            }
        }

        [MigrationsTheory]
        public void Can_scaffold()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                var scaffoldedMigration = facade.Scaffold("Create", "cs", "ClassLibrary1", ignoreChanges: false);

                Assert.True(scaffoldedMigration.DesignerCode.Length > 500);
                Assert.Equal("cs", scaffoldedMigration.Language);
                Assert.True(scaffoldedMigration.MigrationId.EndsWith("_Create"));
                Assert.True(scaffoldedMigration.UserCode.Length > 500);
            }
        }

        [MigrationsTheory]
        public void Can_scaffold_vb()
        {
            ResetDatabase();

            using (var facade = new ToolingFacade(
                "ClassLibrary1",
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                var scaffoldedMigration = facade.Scaffold("Create", "vb", "ClassLibrary1", ignoreChanges: false);

                Assert.True(scaffoldedMigration.DesignerCode.Length > 500);
                Assert.Equal("vb", scaffoldedMigration.Language);
                Assert.True(scaffoldedMigration.MigrationId.EndsWith("_Create"));
                Assert.True(scaffoldedMigration.UserCode.Length > 500);
            }
        }

        [MigrationsTheory]
        public void Wraps_assembly_not_found_exceptions()
        {
            const string unknownAssemblyName = "UnknownAssembly";

            using (var facade = new ToolingFacade(
                unknownAssemblyName,
                "ClassLibrary1.Configuration",
                _projectDir,
                Path.Combine(_projectDir, "App.config"),
                null,
                null))
            {
                Assert.Throws<ToolingException>(() => facade.GetDatabaseMigrations())
                    .ValidateMessage("ToolingFacade_AssemblyNotFound", unknownAssemblyName);
            }
        }

        public void SetFixture(ToolingFixture data)
        {
            _projectDir = data.ProjectDir;
        }
    }

    public class ToolingFixture : IDisposable
    {
        public string ProjectDir { get; private set; }

        public ToolingFixture()
        {
            var targetDir = IOHelpers.GetTempDirName();
            const string targetName = "ClassLibrary1";
            const string targetFileName = targetName + ".dll";
            var targetPath = Path.Combine(targetDir, targetFileName);

            var entityFrameworkPath = new Uri(typeof(DbContext).Assembly.CodeBase).LocalPath;
            IOHelpers.CopyToDir(entityFrameworkPath, targetDir);

            var entityFrameworkSqlServerPath = new Uri(typeof(SqlProviderServices).Assembly.CodeBase).LocalPath;
            IOHelpers.CopyToDir(entityFrameworkSqlServerPath, targetDir);

            using (var compiler = new CSharpCodeProvider())
            {
                var results
                    = compiler.CompileAssemblyFromSource(
                        new CompilerParameters(
                            new[]
                                {
                                    "System.dll",
                                    "System.Data.dll",
                                    "System.Core.dll",
                                    "System.Data.Entity.dll",
                                    entityFrameworkPath
                                },
                            targetPath),
                        @"namespace ClassLibrary1
{
    using System.Data.Common;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Data.Entity.Migrations.History;

    public class Configuration : DbMigrationsConfiguration<Context>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }
    }

    public class Context : DbContext
    {
        public Context()
            : base(""Name=ClassLibrary1"")
        {
        }

        public DbSet<Entity> Entities { get; set; }
    }

    public class CustomHistoryContext : HistoryContext
    {
        public CustomHistoryContext(DbConnection existingConnection, bool contextOwnsConnection, string defaultSchema)
            : base(existingConnection, contextOwnsConnection, defaultSchema)
        {
        }
    }

    public class Entity
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}");

                if (results.Errors.HasErrors)
                {
                    throw new InvalidOperationException(results.Errors.Cast<CompilerError>().First(e => !e.IsWarning).ToString());
                }
            }

            var configurationFile = Path.Combine(targetDir, "App.config");

            File.WriteAllText(
                configurationFile,
                @"<?xml version='1.0' encoding='utf-8' ?>
<configuration>
  <connectionStrings>
    <add name='ClassLibrary1' connectionString='" +
                DatabaseProviderFixture.InitializeTestDatabase(DatabaseProvider.SqlClient, DatabaseProviderFixture.DefaultDatabaseName).
                    ConnectionString +
                @"' providerName='System.Data.SqlClient' />
  </connectionStrings>
</configuration>");

            ProjectDir = targetDir;
        }

        public void Dispose()
        {
            if (ProjectDir != null
                && Directory.Exists(ProjectDir))
            {
                Directory.Delete(ProjectDir, true);
            }
        }
    }
}
