using System.Diagnostics;
using System.Net;
using System.Text;
using System.Web;
using MarketData;
using MarketData.Configuration;
using MarketData.Integration;
using MarketData.Utils;
using Microsoft.Extensions.Configuration;

namespace IPMonitor
{
  public class MainService : IMainService
  {
    /// <summary>
    /// This is the main entry point.
    /// Arguments:  /FORCE:true|false   if true forces a refresh on ZoneEdit.
    /// Force an update to ZoneEdit at the top of every hour
    /// cron every hour
    /// DOTNET_ROOT=/opt/dotnet
    /// CRON_DIR_IPMONITOR=/opt/MarketData/IPMonitor
    /// 0 * * * *  cd $CRON_DIR_IPMONITOR ; /opt/MarketData/IPMonitor/ipmonitor /FORCE:true > /dev/null 2>&1   
    /// cron 10 minute intervals except top of hour
    /// 10,20,30,40,50 * * * *   cd $CRON_DIR_IPMONITOR ; /opt/MarketData/IPMonitor/ipmonitor > /dev/null 2>&1 
    /// </summary>
    /// <param name="args"></param>
    /// <param name="configuration"></param>
    public void RunService(String[] args,IConfiguration configuration)
    {
      Profiler profiler=new Profiler();
      DateTime currentDate = DateTime.Now;
      GlobalConfig.Instance.Configuration = configuration;     // This call sets up configuration stuff so it needs to be first.

      string arg = "ipmonitor";
      CommandArgs commandArgs = new CommandArgs(args);      

      if(!CreateLogging(arg))
      {
        Console.WriteLine("CreateLogging returned false.");
        return;
      }  

      try
      {
        MDTrace.WriteLine(LogLevel.DEBUG,$"[RunService] Started @ {Utility.DateTimeToStringYYYYHMMHDDHHMMSSTT(currentDate)} in {Directory.GetCurrentDirectory()}");
        MDTrace.WriteLine(LogLevel.DEBUG,$"[RunService] Argument {arg}");

        bool force=false;
        if(commandArgs.Has("FORCE"))
        {
          force=commandArgs.Coalesce<bool>("FORCE");
          MDTrace.WriteLine(LogLevel.DEBUG,$"FORCE={force}");
        }
        String ipAddress=GetPublicIPAddress();
        UpdateIPAddress(ipAddress,force);
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
      }
      finally
      {
        MDTrace.WriteLine(LogLevel.DEBUG,$"Done, total took {profiler.End()} (ms)");
      }
    }

    private static bool CreateLogging(String task)
    {
      if(String.IsNullOrEmpty(task))return false;
      task=task.ToLower();
      MDTrace.LogLevel = LogLevel.DEBUG;
      String logFolder = "/logs";
      DateTime currentDate=DateTime.Now;
      String strLogFile = "marketdata_" + task + ".log";
      String currentWorkingDirectory = Directory.GetCurrentDirectory();
      Console.WriteLine($"Current directory is {currentWorkingDirectory}");
      Utility.EnsureLogFolder(currentWorkingDirectory+logFolder);
      Utility.ExpireLogs(currentWorkingDirectory+logFolder,1);
      Trace.Listeners.Remove("Default");
      Console.WriteLine($"Adding Trace Listener :{currentWorkingDirectory+logFolder+"/"+strLogFile}");
      Trace.Listeners.Add(new TextWriterTraceListener(currentWorkingDirectory+logFolder+"/"+strLogFile));
      MDTrace.WriteLine($"Trace Listener added.");
      Utility.ShowLogs(currentWorkingDirectory + logFolder);
      return true;
    }

    /// <summary>
    /// Retains a record of current ip address in ipaddress.txt file and updates that ip address to ZoneEdit for DNS
    /// </summary>
    /// <param name="ipAddress"></param>
    public static void UpdateIPAddress(String ipAddress,bool force)
    {
      try
      {
        String strPathFileName="ipaddress.txt";
        if(null==ipAddress)return;
        if(force)
        {
          File.Delete(GetPathFileName(strPathFileName));
        }
        if(!File.Exists(GetPathFileName(strPathFileName)))
        {
          WriteFile(GetPathFileName(strPathFileName), ipAddress);
          ZoneEditResponses zoneEditResponses=UpdateZoneEditRecord(ipAddress);
          MDTrace.WriteLine(LogLevel.DEBUG,$"IPAddress {ipAddress}.  ZoneEditUpdated={zoneEditResponses.IsSuccess()}");
          SendSMSEmail(String.Format("IPAddress {0}.  ZoneEditUpdated={1}",ipAddress,zoneEditResponses.IsSuccess()));
          if(!zoneEditResponses.IsSuccess())
          {
            File.Delete(GetPathFileName(strPathFileName));
          }
        }
        else
        {
          String currentIPAddress=ReadFile(GetPathFileName(strPathFileName));
          if(null!=currentIPAddress && !currentIPAddress.Equals(ipAddress))
          {
            WriteFile(GetPathFileName(strPathFileName),ipAddress);
            ZoneEditResponses zoneEditResponses=UpdateZoneEditRecord(ipAddress);
            SendSMSEmail(String.Format("IPAddress {0}.  ZoneEditUpdated={1}",ipAddress,zoneEditResponses.IsSuccess()));
          }
          else if(null!=currentIPAddress && currentIPAddress.Equals(ipAddress))
          {
            MDTrace.WriteLine(LogLevel.DEBUG,$"Public IP:{ipAddress} matches latest fetched IP:{currentIPAddress}");
          }
          else if(null==currentIPAddress)
          {
           SendSMSEmail("IPMonitor "+ipAddress+ ".  IPMonitor encountered an issue reading the IPAddress file.");
          }
        }
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
      }
    }

    /// <summary>
    /// Retrieves the public IP Address
    /// </summary>
    /// <param name="ipAddress"></param>
    public static String GetPublicIPAddress()
    {
      int MAX_RETRIES=5;
      int TIMEOUT_BETWEEN_ATTEMPTS=30000;
      String request="http://checkip.dyndns.org/";

      try
      {
         String address = null; 
         for(int index=0;index<MAX_RETRIES && null==address;index++)
         {
           MDTrace.WriteLine(LogLevel.DEBUG,$"Requesting IPAddress from {request}");
           if(!NetworkStatus.IsNetworkAvailable())
           {
             MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The Network seems to be disconnected.  Will retry after {0} (ms) ",request,TIMEOUT_BETWEEN_ATTEMPTS));
             try{Thread.Sleep(TIMEOUT_BETWEEN_ATTEMPTS);}catch{;}
             continue;
           }
           HttpNetResponse response = HttpNetRequest.GetRequestNoEncodingV7(request);
           if(!response.Success)
           {
             MDTrace.WriteLine(LogLevel.DEBUG,$"Request {request} failed with {response.StatusCode}.  Will retry after {TIMEOUT_BETWEEN_ATTEMPTS} (ms) ");
             try{Thread.Sleep(TIMEOUT_BETWEEN_ATTEMPTS);}catch{;}
             continue;
           }
           address = response.ResponseString;
           if(String.IsNullOrEmpty(address))
           {
             MDTrace.WriteLine(LogLevel.DEBUG,$"Request {request} received an empty response.  Will retry after {TIMEOUT_BETWEEN_ATTEMPTS} (ms) ");
             try{Thread.Sleep(TIMEOUT_BETWEEN_ATTEMPTS);}catch{;}
             continue;           
           }
         } 
         if(null==address)
         {
           MDTrace.WriteLine(LogLevel.DEBUG,$"Unable to obtain IP Address.  Attempts:{MAX_RETRIES}");
           return null;
         }
         try
         {
           int first = address.IndexOf("Address: ") + 9;  
           int last = address.LastIndexOf("</body>");  
           address = address.Substring(first, last - first);  
           MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Request succeeded: {0} -> {1}",request,address));
           return address;  
         }
         catch(Exception exception)
         {
           MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception parsing address: {0}",exception.ToString()));
           return null;
         }
       }
       catch(Exception exception)
       {
         String message = String.Format("Exception:{0}",exception.ToString());
         MDTrace.WriteLine(LogLevel.DEBUG,message);
         SendSMSEmail("IPMonitor encountered an issue retrieving public IPAddress.");
         return null;
       }
    }

    /// <summary>
    /// Updates ZoneEdit with the given ipaddress.
    /// </summary>
    /// <param name="ipAddress"></param>
    /// <returns>ZoneEditResponses</returns>
    public static ZoneEditResponses UpdateZoneEditRecord(String ipAddress)
    {
      HttpClient client = default;
      HttpNetResponse httpNetResponse=default;
      ZoneEditResponses zoneEditResponses = new ZoneEditResponses();

      try
      {
        client = new HttpClient();

        String user=GlobalConfig.Instance.Configuration["zoneedit_user"];
        String password=GlobalConfig.Instance.Configuration["zoneedit_password"];
        String host=GlobalConfig.Instance.Configuration["zoneedit_host"];

        String strRequest=null; 
        StringBuilder sb=new StringBuilder();

        sb = new StringBuilder();
        sb.Append("https://");
        sb.Append("dynamic.zoneedit.com/auth/dynamic.html");
        sb.Append("?host=").Append(HttpUtility.UrlEncode(host));
        sb.Append("&dnsto=").Append(HttpUtility.UrlEncode(ipAddress));
        strRequest=sb.ToString();
        httpNetResponse = HttpNetRequest.GetRequestNoEncodingZoneEdit(strRequest,user,password);

        if(httpNetResponse.Success)
        {
          zoneEditResponses = new ZoneEditResponses(httpNetResponse.ResponseString);
        }
        foreach(ZoneEditResponse zoneEditResponse in zoneEditResponses)
        {
          MDTrace.WriteLine(LogLevel.DEBUG,$"Text:{zoneEditResponse.Text} Zone:{zoneEditResponse.Zone}");
        }
        return zoneEditResponses; 
      }
      finally
      {
        if(default!=client)
        {
          client.Dispose();
        }
        if(default!=httpNetResponse)
        {
          httpNetResponse.Dispose();
        }
      }
    }

    /// <summary>
    /// Writes the given ip address into the specified file 
    /// </summary>
    /// <param name="strPathFileName"></param>
    /// <param name="ipAddress"></param>
    public static void WriteFile(String strPathFileName,String ipAddress)
    {
      try
      {
        if(File.Exists(strPathFileName))File.Delete(strPathFileName);
        using FileStream fileStream=new FileStream(strPathFileName,FileMode.Create,FileAccess.Write,FileShare.Read);
        using StreamWriter streamWriter=new StreamWriter(fileStream);
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Creating address file:{0}",strPathFileName));
        streamWriter.WriteLine(ipAddress);
        streamWriter.Flush();
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
        return;
      }
    }

    /// <summary>
    /// Reads a string from the specified file 
    /// </summary>
    /// <param name="strPathFileName"></param>
    /// <param name="ipAddress"></param>
    public static String ReadFile(String strPathFileName)
    {
      try
      {
        using FileStream fileStream=new FileStream(strPathFileName,FileMode.Open,FileAccess.ReadWrite,FileShare.Read);
        using StreamReader streamReader=new StreamReader(fileStream);
        String item=null;
        item=streamReader.ReadLine();
        return item;
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
        return null;
      }
    }

    /// <summary>
    /// Determines the fully qualified path file name for the provided file based on current directory and working folder 
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public static String GetPathFileName(String fileName)
    {
      String currentWorkingDirectory = Directory.GetCurrentDirectory();
      String workingFolder = GlobalConfig.Instance.Configuration["working_folder"];
      if(workingFolder.StartsWith("/"))
      {
        String fullPath = currentWorkingDirectory+workingFolder;
        if(!Directory.Exists(fullPath))Directory.CreateDirectory(fullPath);
        return fullPath+"/"+fileName;
      }
      else
      {
        String fullPath = currentWorkingDirectory+"/"+workingFolder;
        if(!Directory.Exists(fullPath))Directory.CreateDirectory(fullPath);
        return fullPath+"/"+fileName;
      }
    }

    /// <summary>
    /// Sends an email.
    /// </summary>
    /// <param name="message"></param>
    public static void SendSMSEmail(string message)
    {
      String smsSMTPAddress = GlobalConfig.Instance.Configuration["sms_smtpaddress"];
      String smsUserName = GlobalConfig.Instance.Configuration["sms_smsusername"];
      String smsPassword = GlobalConfig.Instance.Configuration["sms_smspassword"];
      String[] smsRecipients = GlobalConfig.Instance.Configuration["sms_smsrecipients"].Split(',');
      bool sendNotifications = bool.Parse(GlobalConfig.Instance.Configuration["send_notifications"]);

      if(!sendNotifications)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,$"Not sending Message '{message}' to {GlobalConfig.Instance.Configuration["sms_smsrecipients"]} because notifications are disabled.");
        return;
      }

      SMSClient.SendSMSEmail(message, smsUserName, smsRecipients, smsSMTPAddress, smsUserName, smsPassword);
    }
  }
}