// CosmosClient, DatabaseResponse, Database, IndexingPolicy i inne 
using Microsoft.Azure.Cosmos;

using Packt.Shared; // NorthwindContext, Product, Customer i inne 
using System.Net; // HttpStatusCode

using Gremlin.Net.Driver; // GremlinServer, GremlinClient, ResultSet<T>
using Gremlin.Net.Structure.IO.GraphSON; // GraphSON2Reader, GraphSON2Writer
using Newtonsoft.Json; // JsonConvert
using System.Globalization;

partial class Program
{
  private static bool useLocal = false;

  private static string RequestChargeHeader = "x-ms-request-charge";

  // Właściwości wykorzystywane do obsługi bazy Azure Cosmos DB w lokalnym emulatorze.
  private static string endpointUriLocal = "https://localhost:8081/";
  private static string primaryKeyLocal = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";

  // Właściwości wykorzystywane do obsługi bazy Azure Cosmos DB w chmurze.
  private static string account =
    "apps-services-net7-graph"; // Użyj swojego konta.

  private static string primaryKeyCloud
    = "qeYFsBVUR77I5dJS2OhP83jjyrYUn4HuOZV8u3VkkChaluXeM0EDhRNPDUsxt6mQRe0HY5CeaM3ZR86mJtry2g=="; // Użyj swojego klucza.

  private static string endpointUriCloud =
    $"https://{account}.documents.azure.com:443/";

  // Właściwości wykorzystywane przez klienta Gremlina w lokalnym emulatorze.
  private static string hostLocal = "localhost";
  private static int portLocal = 8081;

  // Właściwości wykorzystywane przez klienta Gremlina w chmurze.
  private static string hostCloud =
    $"{account}.gremlin.cosmos.azure.com";

  private static int portCloud = 443;

  // Typowe nazwy
  private static string database = "NorthwindGraphDb";
  private static string collection = "CustomerProductViews";

  public static GremlinServer gremlinServer = new(
    hostname: useLocal ? hostLocal : hostCloud,
    port: useLocal ? portLocal : portCloud,
    enableSsl: true,
    username: $"/dbs/" + database + "/colls/" + collection,
    password: useLocal ? primaryKeyLocal : primaryKeyCloud);

  static async Task<(int, double)> ExecuteGremlinScript(string script)
  {
    int affected = 0;
    double requestChargeTotal = 0.0;

    try
    {
      using (GremlinClient gremlinClient = new(
        gremlinServer,
        new GraphSON2Reader(),
        new GraphSON2Writer(),
        GremlinClient.GraphSON2MimeType))
      {
        SectionTitle("Skrypt zapytania Gremlin");
        WriteLine(script);

        ResultSet<dynamic> resultSet = await gremlinClient
          .SubmitAsync<dynamic>(script);

        affected = resultSet.Count;

        if (double.TryParse(resultSet.StatusAttributes[
          RequestChargeHeader].ToString(), out double requestCharge))
        {
          requestChargeTotal += requestCharge;
        }

        if (affected > 0)
        {
          SectionTitle($"liczba zgodnych: {affected}");
          foreach (dynamic result in resultSet)
          {
            string jsonOutput = JsonConvert.SerializeObject(result);
            WriteLine(jsonOutput);
          }
        }
      }
    }
    catch (Exception ex)
    {
      WriteLine("Błąd {0}, komunikat {1}", ex.GetType(), ex.Message);
    }

    return (affected, requestChargeTotal);
  }

  static async Task CreateCosmosGraphResources()
  {
    SectionTitle("Tworzenie zasobów bazy grafowej Cosmos");

    try
    {
      using (CosmosClient client = new(
        accountEndpoint: useLocal ? endpointUriLocal : endpointUriCloud,
        authKeyOrResourceToken: useLocal ? endpointUriLocal : primaryKeyCloud))
      {
        SectionTitle("Szczegóły klienta bazy Cosmos:");
        WriteLine($"  Uri: {client.Endpoint}");

        DatabaseResponse dbResponse = await client
          .CreateDatabaseIfNotExistsAsync(
            database, throughput: 400 /* RU/s */);

        string status = dbResponse.StatusCode switch
        {
          HttpStatusCode.OK => "istnieje",
          HttpStatusCode.Created => "utworzona",
          _ => "nieznany",
        };

        WriteLine("ID bazy danych: {0}, status: {1}.",
          arg0: dbResponse.Database.Id, arg1: status);

        IndexingPolicy indexingPolicy = new()
        {
          IndexingMode = IndexingMode.Consistent,
          Automatic = true, // Elementy są indeksowane, chyba że zostaną jawnie wykluczone.
          IncludedPaths = { new IncludedPath { Path = "/*" } }
        };

        ContainerProperties containerProperties = new(collection,
          partitionKeyPath: "/partitionKey")
        {
          IndexingPolicy = indexingPolicy
        };

        ContainerResponse containerResponse = await dbResponse.Database
          .CreateContainerIfNotExistsAsync(
            containerProperties, throughput: 1000 /* RU/s */);

        status = dbResponse.StatusCode switch
        {
          HttpStatusCode.OK => "istnieje",
          HttpStatusCode.Created => "utworzony",
          _ => "nieznany",
        };

        WriteLine("ID kontenera: {0}, stan: {1}.",
          arg0: containerResponse.Container.Id, arg1: status);

        Container container = containerResponse.Container;

        ContainerProperties properties = await container.ReadContainerAsync();
        WriteLine($"  PartitionKeyPath: {properties.PartitionKeyPath}");
        WriteLine($"  LastModified: {properties.LastModified}");
        WriteLine("  IndexingPolicy.IndexingMode: {0}",
          arg0: properties.IndexingPolicy.IndexingMode);
        WriteLine("  IndexingPolicy.IncludedPaths: {0}",
          arg0: string.Join(",", properties.IndexingPolicy
            .IncludedPaths.Select(path => path.Path)));
      }
    }
    catch (HttpRequestException ex)
    {
      WriteLine("Błąd: {0}", arg0: ex.Message);
      WriteLine("Wskazówka: sprawdź czy emulator Azure Cosmos DB jest uruchomiony.");
    }
    catch (Exception ex)
    {
      WriteLine("Błąd: {0}, komunikat: {1}",
        arg0: ex.GetType(),
        arg1: ex.Message);
    }
  }

  static async Task CreateProductVertices()
  {
    double totalCharge = 0.0;

    try
    {
      using (NorthwindContext db = new())
      {
        SectionTitle("Tworzenie wierzchołków produktów");

        foreach (Product p in db.Products)
        {
          string getProduct = $"""
              g.V().hasLabel("product")
                .has("productId", {p.ProductId})
              """;

          (int Count, double Cost) found =
            await ExecuteGremlinScript(getProduct);

          totalCharge += found.Cost;

          if (found.Count == 0)
          {
            WriteLine("Nie znaleziono produktu {0}. Tworzenie jego wierzchołka...",
              p.ProductName);

            string addProduct = $"""
              g.addV("product")
                .property("partitionKey", {p.ProductId})
                .property("productId", {p.ProductId})
                .property("productName", "{p.ProductName}")
                .property("quantityPerUnit", "{p.QuantityPerUnit}")
                .property("unitPrice", {p.UnitPrice?.ToString(CultureInfo.InvariantCulture) ?? 0})
                .property("unitsInStock", {p.UnitsInStock ?? 0})
                .property("reorderLevel", {p.ReorderLevel ?? 0})
                .property("unitsOnOrder", {p.UnitsOnOrder ?? 0})
                .property("discontinued", {p.Discontinued.ToString().ToLower()})
              """;

            (int Count, double Cost) added =
              await ExecuteGremlinScript(addProduct);

            totalCharge += added.Cost;

            WriteLine("Wierzchołek {0} utworzony, koszt: {1} RU.",
              added.Count, added.Cost);
          }
          else
          {
            WriteLine($"Produkt {p.ProductId} już istnieje.");
          }
        }
      }
    }
    catch (Exception ex)
    {
      OutputException(ex);
    }

    WriteLine("Całkowita liczba wykorzystanych jednostek: RU: {0:N2}", totalCharge);
  }

  static async Task CreateCustomerVertices()
  {
    double totalCharge = 0.0;

    try
    {
      using (NorthwindContext db = new())
      {
        SectionTitle("Tworzenie wierzchołków klientów");

        foreach (Customer c in db.Customers)
        {
          string getCustomer = $"""
              g.V().hasLabel("customer")
                .has("customerId", "{c.CustomerId}")
              """;

          (int Count, double Cost) found =
            await ExecuteGremlinScript(getCustomer);

          totalCharge += found.Cost;

          if (found.Count == 0)
          {
            WriteLine("Nie znaleziono klienta {0}. Tworzenie jego wierzchołka...",
              c.CompanyName);

            string addCustomer = $"""
              g.addV("customer")
                .property("partitionKey", "{c.CustomerId}")
                .property("customerId", "{c.CustomerId}")
                .property("companyName", "{c.CompanyName}")
                .property("contactName", "{c.ContactName}")
                .property("contactTitle", "{c.ContactTitle}")
                .property("address", "{c.Address}")
                .property("city", "{c.City}")
                .property("region", "{c.Region}")
                .property("postalCode", "{c.PostalCode}")
                .property("country", "{c.Country}")
                .property("phone", "{c.Phone}")
                .property("fax", "{c.Fax}")
              """;

            (int Count, double Cost) added =
              await ExecuteGremlinScript(addCustomer);

            totalCharge += added.Cost;

            WriteLine("Wierzchołek {0} utworzony, koszt: {1} RU.",
              added.Count, added.Cost);
          }
          else
          {
            WriteLine($"Klient {c.CustomerId} już istnieje.");
          }
        }
      }
    }
    catch (Exception ex)
    {
      OutputException(ex);
    }

    WriteLine("Całkowita liczba wykorzystanych jednostek: RU: {0:N2}", totalCharge);
  }
}
