using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Threading;

namespace SparkleLib {

    public abstract class SparkleFetcherSSH : SparkleFetcherBase {

        public SparkleFetcherSSH (SparkleFetcherInfo info) : base (info)
        {
        }


        public override bool Fetch ()
        {
            if (RemoteUrl.Host.EndsWith (".onion")) {
                // Tor has special domain names called ".onion addresses".  They can only be
                // resolved by using a proxy via tor. While the rest of the openssh suite
                // fully supports proxying, ssh-keyscan does not, so we can't use it for .onion
                SparkleLogger.LogInfo ("Auth", "using tor .onion address skipping ssh-keyscan");

            } else if (!RemoteUrl.Scheme.StartsWith ("http")) {
                string host_key = FetchHostKey ();
                
                if (string.IsNullOrEmpty (RemoteUrl.Host) || host_key == null) {
                    SparkleLogger.LogInfo ("Auth", "Could not fetch host key");
                    this.errors.Add ("error: Could not fetch host key");
                    
                    return false;
                }
                
                bool warn = true;
                if (RequiredFingerprint != null) {
                    string host_fingerprint;

                    try {
                        host_fingerprint = DeriveFingerprint (host_key);
                    
                    } catch (InvalidOperationException e) {
                        // "Unapproved cryptographic algorithms" won't work when FIPS is enabled on Windows.
                        // Software like Cisco AnyConnect can demand this feature is on, so we show an error
                        SparkleLogger.LogInfo ("Auth", "Unable to derive fingerprint: ", e);
                        this.errors.Add ("error: Can't check fingerprint due to FIPS being enabled");
                        
                        return false;
                    }

                    if (host_fingerprint == null || !RequiredFingerprint.Equals (host_fingerprint)) {
                        SparkleLogger.LogInfo ("Auth", "Fingerprint doesn't match");
                        this.errors.Add ("error: Host fingerprint doesn't match");
                        
                        return false;
                    }
                    
                    warn = false;
                    SparkleLogger.LogInfo ("Auth", "Fingerprint matches");
                    
                } else {
                    SparkleLogger.LogInfo ("Auth", "Skipping fingerprint check");
                }
                
                AcceptHostKey (host_key, warn);
            }
            
            return true;
        }


        private string FetchHostKey ()
        {
            SparkleLogger.LogInfo ("Auth", "Fetching host key for " + RemoteUrl.Host);
            
            Process process = new Process ();
            process.StartInfo.FileName               = "ssh-keyscan";
            process.StartInfo.WorkingDirectory       = SparkleConfig.DefaultConfig.TmpPath;
            process.StartInfo.UseShellExecute        = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.CreateNoWindow         = true;
            process.EnableRaisingEvents              = true;
            
            string [] key_types = {"rsa", "dsa", "ecdsa"};
            
            foreach (string key_type in key_types) {
                if (RemoteUrl.Port < 1)
                    process.StartInfo.Arguments = "-t " + key_type + " -p 22 " + RemoteUrl.Host;
                else
                    process.StartInfo.Arguments = "-t " + key_type + " -p " + RemoteUrl.Port + " " + RemoteUrl.Host;
                
                SparkleLogger.LogInfo ("Cmd", process.StartInfo.FileName + " " + process.StartInfo.Arguments);
                
                process.Start ();
                string host_key = process.StandardOutput.ReadToEnd ().Trim ();
                process.WaitForExit ();
                
                if (process.ExitCode == 0 && !string.IsNullOrWhiteSpace (host_key))
                    return host_key;
            }
            
            return null;
        }
        
        
        private string DeriveFingerprint (string public_key)
        {
            try {
                MD5 md5            = new MD5CryptoServiceProvider ();
                string key         = public_key.Split (" ".ToCharArray ()) [2];
                byte [] b64_bytes  = Convert.FromBase64String (key);
                byte [] md5_bytes  = md5.ComputeHash (b64_bytes);
                string fingerprint = BitConverter.ToString (md5_bytes);
                
                return fingerprint.ToLower ().Replace ("-", ":");
                
            } catch (Exception e) {
                SparkleLogger.LogInfo ("Fetcher", "Failed creating fingerprint: " + e.Message + " " + e.StackTrace);
                return null;
            }
        }
        
        
        private void AcceptHostKey (string host_key, bool warn)
        {
            string ssh_config_path       = Path.Combine (SparkleConfig.DefaultConfig.HomePath, ".ssh");
            string known_hosts_file_path = Path.Combine (ssh_config_path, "known_hosts");
            
            if (!File.Exists (known_hosts_file_path)) {
                if (!Directory.Exists (ssh_config_path))
                    Directory.CreateDirectory (ssh_config_path);
                
                File.Create (known_hosts_file_path).Close ();
            }
            
            string host                 = RemoteUrl.Host;
            string known_hosts          = File.ReadAllText (known_hosts_file_path);
            string [] known_hosts_lines = File.ReadAllLines (known_hosts_file_path);
            
            foreach (string line in known_hosts_lines) {
                if (line.StartsWith (host + " "))
                    return;
            }
            
            if (known_hosts.EndsWith ("\n"))
                File.AppendAllText (known_hosts_file_path, host_key + "\n");
            else
                File.AppendAllText (known_hosts_file_path, "\n" + host_key + "\n");
            
            SparkleLogger.LogInfo ("Auth", "Accepted host key for " + host);
            
            if (warn)
                this.warnings.Add ("The following host key has been accepted:\n" + DeriveFingerprint (host_key));
        }
    }
}
