using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using cartservice.cartstore;
using cartservice.interfaces;
using CommandLine;
using Grpc.Core;
using Microsoft.Extensions.Configuration;

namespace cartservice
{
    class Program
    {
        const string CART_SERVICE_ADDRESS = "LISTEN_ADDR";
        const string REDIS_ADDRESS = "REDIS_ADDR";
        const string CART_SERVICE_PORT = "PORT";

        [Verb("start", HelpText = "Starts the server listening on provided port")]
        class ServerOptions
        {
            [Option('h', "hostname", HelpText = "The ip on which the server is running. If not provided, LISTEN_ADDR environment variable value will be used. If not defined, localhost is used")]
            public string Host { get; set; }

            [Option('p', "port", HelpText = "The port on for running the server")]
            public int Port { get; set; }

            [Option('r', "redis", HelpText = "The ip of redis cache")]
            public string Redis { get; set; }
        }

        static object StartServer(string host, int port, ICartStore cartStore)
        {
            // Run the server in a separate thread and make the main thread busy waiting.
            // The busy wait is because when we run in a container, we can't use techniques such as waiting on user input (Console.Readline())
            Task.Run(async () =>
            {
                Console.WriteLine($"Trying to start a grpc server at  {host}:{port}");
                Server server = new Server
                {
                    Services = { Hipstershop.CartService.BindService(new CartServiceImpl(cartStore)) },
                    Ports = { new ServerPort(host, port, ServerCredentials.Insecure) }
                };

                Console.WriteLine($"Cart server is listening at {host}:{port}");
                server.Start();

                await cartStore.InitializeAsync();
            });

            // Busy wait to keep the process alive
            while(true)
            {
                Thread.Sleep(TimeSpan.FromMinutes(10));
            }
        }

        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Invalid number of arguments supplied");
                Environment.Exit(-1);
            }

            switch (args[0])
            {
                case "start":
                    Parser.Default.ParseArguments<ServerOptions>(args).MapResult(
                        (ServerOptions options) => 
                        {
                            // Set hostname/ip address
                            string hostname = options.Host;
                            if (string.IsNullOrEmpty(hostname))
                            {
                                Console.WriteLine($"Reading host address from {CART_SERVICE_ADDRESS} environment variable");
                                hostname = Environment.GetEnvironmentVariable(CART_SERVICE_ADDRESS);
                                if (string.IsNullOrEmpty(hostname))
                                {
                                    Console.WriteLine($"Environment variable {CART_SERVICE_ADDRESS} was not set. Setting the host to 0.0.0.0");
                                    hostname = "0.0.0.0";
                                }
                            }

                            // Set the port
                            int port = options.Port;
                            if (options.Port <= 0)
                            {
                                Console.WriteLine($"Reading cart service port from {CART_SERVICE_PORT} environment variable");
                                string portStr = Environment.GetEnvironmentVariable(CART_SERVICE_PORT);
                                if (string.IsNullOrEmpty(portStr))
                                {
                                    Console.WriteLine($"{CART_SERVICE_PORT} environment variable was not set. Setting the port to 8080");
                                    port = 8080;
                                }
                                else    
                                {
                                    port = int.Parse(portStr);
                                }
                            }

                            // Set redis cache host (hostname+port)
                            ICartStore cartStore;
                            string redis = ReadRedisAddress(options.Redis);

                            // Redis was specified via command line or environment variable
                            if (!string.IsNullOrEmpty(redis))
                            {
                                cartStore = new RedisCartStore(redis);
                                return StartServer(hostname, port, cartStore);
                            }
                            else
                            {
                                Console.WriteLine("Redis cache host(hostname+port) was not specified. Starting a cart service using local store");
                                Console.WriteLine("If you wanted to use Redis Cache as a backup store, you should provide its address via command line or REDIS_ADDRESS environment variable.");
                                cartStore = new LocalCartStore();
                            }

                            return StartServer(hostname, port, cartStore);
                        },
                        errs => 1);
                    break;
                default:
                    Console.WriteLine("Invalid command");
                    break;
            }
        }

        private static string ReadRedisAddress(string address)
        {
            if (!string.IsNullOrEmpty(address))
            {
                return address;
            }

            Console.WriteLine($"Reading redis cache address from environment variable {REDIS_ADDRESS}");
            string redis = Environment.GetEnvironmentVariable(REDIS_ADDRESS);
            if (!string.IsNullOrEmpty(redis))
            {
                return redis;
            }

            return null;
        }
    }
}
