using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EntityFrameworkCore.Jet.Data.Tests
{
    // TODO: Call all tests for ODBC and OLE DB.
    [TestClass]
    public class JetInformationSchemaTest
    {
        private const string StoreName = nameof(JetInformationSchemaTest) + ".accdb";

        [ClassInitialize]
        public static void TestFixtureSetup(TestContext context)
        {
            using var connection = Helpers.CreateAndOpenDatabase(StoreName);
            
            var scriptPath = Path.Combine(Path.GetDirectoryName(typeof(JetInformationSchemaTest).Assembly.Location) ?? string.Empty, "Northwind.sql");
            var script = File.ReadAllText(scriptPath);
            Helpers.ExecuteScript(connection, script);
        }

        [ClassCleanup]
        public static void TestFixtureTearDown()
        {
            Helpers.DeleteDatabase(StoreName);
        }

        private void AssertDataReaderContent(string actual, string expected)
            => Assert.AreEqual(expected.Trim(), actual);
        
        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void Tables(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(connection, @"SELECT * FROM `INFORMATION_SCHEMA.TABLES`
WHERE TABLE_TYPE <> 'SYSTEM TABLE'
ORDER BY TABLE_NAME");
            
            AssertDataReaderContent(result, $@"
`TABLE_NAME` | `TABLE_TYPE` | `VALIDATION_RULE` | `VALIDATION_TEXT`
--- | --- | --- | ---
{JetConnection.DefaultDualTableName} | INTERNAL TABLE |  | 
Alphabetical list of products | VIEW |  | 
Categories | BASE TABLE |  | 
Category Sales for 1997 | VIEW |  | 
Current Product List | VIEW |  | 
Customer and Suppliers by City | VIEW |  | 
CustomerCustomerDemo | BASE TABLE |  | 
CustomerDemographics | BASE TABLE |  | 
Customers | BASE TABLE |  | 
Employees | BASE TABLE |  | 
EmployeeTerritories | BASE TABLE |  | 
Invoices | VIEW |  | 
Order Details | BASE TABLE |  | 
Order Details Extended | VIEW |  | 
Order Subtotals | VIEW |  | 
Orders | BASE TABLE |  | 
Orders Qry | VIEW |  | 
Product Sales for 1997 | VIEW |  | 
Products | BASE TABLE |  | 
Products Above Average Price | VIEW |  | 
Products by Category | VIEW |  | 
Quarterly Orders | VIEW |  | 
Region | BASE TABLE |  | 
Sales by Category | VIEW |  | 
Sales Totals by Amount | VIEW |  | 
Shippers | BASE TABLE |  | 
Summary of Sales by Quarter | VIEW |  | 
Summary of Sales by Year | VIEW |  | 
Suppliers | BASE TABLE |  | 
Ten Most Expensive Products | VIEW |  | 
Territories | BASE TABLE |  |");
        }
        
        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void Columns(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(
                connection,
                @"SELECT * FROM `INFORMATION_SCHEMA.COLUMNS`
WHERE TABLE_NAME IN ('Orders', 'Order Details Extended', 'Categories')
ORDER BY TABLE_NAME,
         COLUMN_NAME");
            
            AssertDataReaderContent(result, @"
`TABLE_NAME` | `COLUMN_NAME` | `ORDINAL_POSITION` | `DATA_TYPE` | `IS_NULLABLE` | `CHARACTER_MAXIMUM_LENGTH` | `NUMERIC_PRECISION` | `NUMERIC_SCALE` | `COLUMN_DEFAULT` | `VALIDATION_RULE` | `VALIDATION_TEXT` | `IDENTITY_SEED` | `IDENTITY_INCREMENT`
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
Categories | CategoryID | 0 | counter | False |  |  |  |  |  |  | 9 | 1
Categories | CategoryName | 1 | varchar | False | 15 |  |  |  |  |  |  | 
Categories | Description | 2 | longchar | True |  |  |  |  |  |  |  | 
Categories | Picture | 3 | longbinary | True |  |  |  |  |  |  |  | 
Order Details Extended | Discount | 5 | single | False |  |  |  |  |  |  |  | 
Order Details Extended | ExtendedPrice | 6 | currency | True |  |  |  |  |  |  |  | 
Order Details Extended | OrderID | 0 | integer | False |  |  |  |  |  |  |  | 
Order Details Extended | ProductID | 1 | integer | False |  |  |  |  |  |  |  | 
Order Details Extended | ProductName | 2 | varchar | False | 40 |  |  |  |  |  |  | 
Order Details Extended | Quantity | 4 | smallint | False |  |  |  |  |  |  |  | 
Order Details Extended | UnitPrice | 3 | currency | False |  |  |  |  |  |  |  | 
Orders | CustomerID | 1 | char | True | 5 |  |  |  |  |  |  | 
Orders | EmployeeID | 2 | integer | True |  |  |  |  |  |  |  | 
Orders | Freight | 7 | decimal | True |  | 18 | 2 | 0 |  |  |  | 
Orders | OrderDate | 3 | datetime | True |  |  |  |  |  |  |  | 
Orders | OrderID | 0 | counter | False |  |  |  |  |  |  | 11078 | 1
Orders | RequiredDate | 4 | datetime | True |  |  |  |  |  |  |  | 
Orders | ShipAddress | 9 | varchar | True | 60 |  |  |  |  |  |  | 
Orders | ShipCity | 10 | varchar | True | 15 |  |  |  |  |  |  | 
Orders | ShipCountry | 13 | varchar | True | 15 |  |  |  |  |  |  | 
Orders | ShipName | 8 | varchar | True | 40 |  |  |  |  |  |  | 
Orders | ShippedDate | 5 | datetime | True |  |  |  |  |  |  |  | 
Orders | ShipPostalCode | 12 | varchar | True | 10 |  |  |  |  |  |  | 
Orders | ShipRegion | 11 | varchar | True | 15 |  |  |  |  |  |  | 
Orders | ShipVia | 6 | integer | True |  |  |  |  |  |  |  |");
        }

        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void Indexes(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(
                connection,
                @"SELECT * FROM `INFORMATION_SCHEMA.INDEXES`
WHERE TABLE_NAME = 'Orders' OR 
      TABLE_NAME = 'Order Details'
ORDER BY TABLE_NAME,
         INDEX_NAME");
            
            AssertDataReaderContent(result, @"
`TABLE_NAME` | `INDEX_NAME` | `INDEX_TYPE` | `IS_NULLABLE` | `IGNORES_NULLS`
--- | --- | --- | --- | ---
Order Details | FK_Order_Details_Orders | INDEX | True | False
Order Details | FK_Order_Details_Products | INDEX | True | False
Order Details | OrderID | INDEX | True | False
Order Details | OrdersOrder_Details | INDEX | True | False
Order Details | PK_Order_Details | PRIMARY | False | False
Order Details | ProductID | INDEX | True | False
Order Details | ProductsOrder_Details | INDEX | True | False
Orders | CustomerID | INDEX | True | False
Orders | CustomersOrders | INDEX | True | False
Orders | EmployeeID | INDEX | True | False
Orders | EmployeesOrders | INDEX | True | False
Orders | FK_Orders_Customers | INDEX | True | False
Orders | FK_Orders_Employees | INDEX | True | False
Orders | FK_Orders_Shippers | INDEX | True | False
Orders | OrderDate | INDEX | True | False
Orders | PK_Orders | PRIMARY | False | False
Orders | ShippedDate | INDEX | True | False
Orders | ShippedDateDescending | INDEX | True | False
Orders | ShippersOrders | INDEX | True | False
Orders | ShipPostalCode | INDEX | True | False");
        }

        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void IndexColumns(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(
                connection,
                @"SELECT * FROM `INFORMATION_SCHEMA.INDEX_COLUMNS`
WHERE TABLE_NAME = 'Orders' OR 
      TABLE_NAME = 'Order Details'
ORDER BY TABLE_NAME,
         INDEX_NAME,
         ORDINAL_POSITION");
            
            AssertDataReaderContent(result, @"
`TABLE_NAME` | `INDEX_NAME` | `ORDINAL_POSITION` | `COLUMN_NAME` | `IS_DESCENDING`
--- | --- | --- | --- | ---
Order Details | FK_Order_Details_Orders | 0 | OrderID | False
Order Details | FK_Order_Details_Products | 0 | ProductID | False
Order Details | OrderID | 0 | OrderID | False
Order Details | OrdersOrder_Details | 0 | OrderID | False
Order Details | PK_Order_Details | 0 | OrderID | False
Order Details | PK_Order_Details | 1 | ProductID | False
Order Details | ProductID | 0 | ProductID | False
Order Details | ProductsOrder_Details | 0 | ProductID | False
Orders | CustomerID | 0 | CustomerID | False
Orders | CustomersOrders | 0 | CustomerID | False
Orders | EmployeeID | 0 | EmployeeID | False
Orders | EmployeesOrders | 0 | EmployeeID | False
Orders | FK_Orders_Customers | 0 | CustomerID | False
Orders | FK_Orders_Employees | 0 | EmployeeID | False
Orders | FK_Orders_Shippers | 0 | ShipVia | False
Orders | OrderDate | 0 | OrderDate | False
Orders | PK_Orders | 0 | OrderID | False
Orders | ShippedDate | 0 | ShippedDate | False
Orders | ShippedDateDescending | 0 | ShippedDate | True
Orders | ShippersOrders | 0 | ShipVia | False
Orders | ShipPostalCode | 0 | ShipPostalCode | False");
        }
        
        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void Relations(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(
                connection,
                @"SELECT * FROM `INFORMATION_SCHEMA.RELATIONS`
WHERE PRINCIPAL_TABLE_NAME = 'Orders' AND 
      REFERENCING_TABLE_NAME = 'Order Details'
ORDER BY RELATION_NAME");
            
            AssertDataReaderContent(result, @"
`RELATION_NAME` | `REFERENCING_TABLE_NAME` | `PRINCIPAL_TABLE_NAME` | `RELATION_TYPE` | `ON_DELETE` | `ON_UPDATE` | `IS_ENFORCED` | `IS_INHERITED`
--- | --- | --- | --- | --- | --- | --- | ---
FK_Order_Details_Orders | Order Details | Orders | MANY | NO ACTION | NO ACTION | True | True");
        }

        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void RelationColumns(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(
                connection,
                @"SELECT * FROM `INFORMATION_SCHEMA.RELATION_COLUMNS`
WHERE RELATION_NAME = 'FK_Order_Details_Orders'
ORDER BY RELATION_NAME, REFERENCING_COLUMN_NAME, PRINCIPAL_COLUMN_NAME");
            
            AssertDataReaderContent(result, @"
`RELATION_NAME` | `REFERENCING_COLUMN_NAME` | `PRINCIPAL_COLUMN_NAME` | `ORDINAL_POSITION`
--- | --- | --- | ---
FK_Order_Details_Orders | OrderID | OrderID | 1");
        }

        [DataTestMethod]
        [DataRow(DataAccessProviderType.Odbc)]
        [DataRow(DataAccessProviderType.OleDb)]
        public void CheckConstraints(DataAccessProviderType providerType)
        {
            using var connection = Helpers.OpenDatabase(StoreName, providerType);
            var result = Helpers.GetDataReaderContent(
                connection,
                @"SELECT * FROM `INFORMATION_SCHEMA.CHECK_CONSTRAINTS`
ORDER BY TABLE_NAME, CONSTRAINT_NAME");
            
            AssertDataReaderContent(result, $@"
`TABLE_NAME` | `CONSTRAINT_NAME` | `CHECK_CLAUSE`
--- | --- | ---
{JetConnection.DefaultDualTableName} | SingleRecord | (SELECT COUNT(*) FROM [{JetConnection.DefaultDualTableName}]) = 1
Employees | CK_BirthDate | [BirthDate] < NOW()
Order Details | CK_Discount | [Discount] >= 0 and ([Discount] <= 1)
Order Details | CK_Quantity | [Quantity] > 0
Order Details | CK_UnitPrice | [UnitPrice] >= 0
Products | CK_Products_UnitPrice | [UnitPrice] >= 0
Products | CK_ReorderLevel | [ReorderLevel] >= 0
Products | CK_UnitsInStock | [UnitsInStock] >= 0
Products | CK_UnitsOnOrder | [UnitsOnOrder] >= 0");
        }
    }
}