import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;

import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;

import os.path.Pathutil;
import bsh.StringUtil;

import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.google.common.io.Files;


public class iTunesDeveloperInterface extends SFSeleniumBase
{
	private static final String PWD = "QaarxOsuB5N7v447D4KT";
	private static final String PROVISIONINGPROFILENAMEINPUTSEL = "input[name=\"provisioningProfileName\"]";
	private DirData data;

	public boolean loggedIn = false;
	
	private final String APPID_NAME_SELECTOR = ".text-input.name.validate";
	private final String CERTDOWNLOAD_LINK = "a[href*=\"/account/ios/certificate/downloadCer\"]";
	
	private final String PROFILE_DOWNLOADER_LINK = "[href*=\"/account/ios/profile/downloadProfileContent\"]";
	private final long WAITTIME = 3800;

	public iTunesDeveloperInterface(DirData data) {
		FirefoxProfile downloady = this.newFirefoxProfile();//new FirefoxProfile();
		downloady.setPreference("browser.download.manager.showWhenStarting", false);
		downloady.setPreference("browser.download.dir", data.getDataDir().getParentFile().getAbsolutePath() );
		downloady.setPreference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream");
		downloady.setPreference("browser.download.folderList", 2);
		downloady.setPreference("xpinstall.signatures.required", false);
		
		
		this.browser = new FirefoxDriver(downloady);
		this.data = data;
		//this(new FirefoxDriver( downloady ));

	}
	public iTunesDeveloperInterface(AppData data)
	{
		this((DirData)data);
	}
	
	/**
	 * Constructor, aka __init__
	 
	public iTunesDeveloperInterface(WebDriver existingdriver) {
		browser = existingdriver;
	}*/
	
	private void beLoggedIn()
	{
		if (!this.loggedIn)
		{
			this.browser.get("http://developer.apple.com/account/ios/");
			try
			{
				this.waitFor("#accountname");
			} catch (Exception e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			WebElement elementuser = this.q("#accountname");
			WebElement elementpass = this.q("#accountpassword");
			elementuser.sendKeys("appdev@sharefaith.com");
			elementpass.sendKeys(PWD);
			this.q("input[type=submit]").click();
			this.loggedIn = true;
		}
	}
	
	
	public void createAppID() throws Exception
	{
		beLoggedIn();
		String name = data.getTitle();
		String bundleID = data.getId();
		
		this.browser.get("https://developer.apple.com/account/ios/identifiers/bundle/create");
		String simplename = name;
		
		//This doesn't work with comma, - etc.:
		simplename = simplename.replaceAll("[^A-Za-z0-9]", "");
		if (simplename.length() < 1)
		{
			throw new Exception("bad name");
		}
		waitFor(APPID_NAME_SELECTOR);
		this.q(APPID_NAME_SELECTOR).sendKeys(simplename);
		
		//Set explicit id, bundleid
		this.q(".radio-wrapper.explicit input.radio").click();
		this.q(".radio-wrapper.explicit input.appIdentifierString").sendKeys(bundleID);
		this.q("input[type=checkbox][name=push]").click();
		Thread.sleep(WAITTIME);
		this.q(".button.blue[type=submit]").click();
		this.waitFor(".item-details div div.status.partial");
		//TODO what to wait for now      sleep(WAITTIME);
		this.q(".button.blue[type=submit]").click();
		//this.waitForXpath("//h1[contains(., 'Registration complete')]");
	}
	
	private void startCert() throws Exception
	{
		beLoggedIn();
		String name = this.data.getTitle();
		String bundleID = this.data.getId();

		this.browser.get("https://developer.apple.com/account/ios/identifier/bundle");
		this.waitFor(".toolbar-button.search");
		this.q(".toolbar-button.search").click();
		this.waitFor("#certificate-list-search");
		this.q("#certificate-list-search").sendKeys( bundleID + "\n" );
		this.waitFor("#grid-table tr.ui-widget-content");
		this.q("#grid-table tr.ui-widget-content").click();
		Thread.sleep(WAITTIME);
		this.q("tr >td >.row-details >.section >a.button.small.edit-button span").click();
		Thread.sleep(WAITTIME);
		this.waitFor("div:last-of-type >.createCertificate >a.button");
		this.q(      "div:last-of-type >.createCertificate >a.button").click();
		
		Thread.sleep(WAITTIME);
		this.q(".blue.button.submit").click();
		//file(os.path.expanduser('~/Desktop/CreateSigReq.sikuli/commonname.txt'), 'w').write(name)
	}
	
	public void createPushCert() throws Exception
	{
		String name = this.data.getTitle();
		startCert();
		if (name.indexOf(' ') == -1)
		{
			SikuliRunner.run(Pathutil.expanduser("~/Desktop/CreateSigReq.sikuli") + " --args " + name + "");
		} else {
			SikuliRunner.run(Pathutil.expanduser("~/Desktop/CreateSigReq.sikuli") + " --args \"" + name + "\"");
		}
		//CertSigningrequest of this one is in desktop now.
		File myCSR = new File(data.getDataDir().getParentFile(), "CertificateSigningRequest.certSigningRequest");
		//move myCSR to final destination:
		Files.move(Pathutil.expanduser("~/Desktop/CertificateSigningRequest.certSigningRequest"), myCSR);
		this.uploadDownloadCert(myCSR);
		File output = new File(data.getDataDir().getParentFile(), "aps.cer");
		this.waitForFile(output);
		Pathutil.osOpen(output);
		//TODO can export the cert with cmd: security find-certificate -c "com.sharefaith.churchapp.19924df2a49e7bde" 
		// but can this be exported to .p12 file?
		SikuliRunner.run( Pathutil.expanduser("~/Desktop/ExportCertP12.sikuli") + " --args " + this.data.getId() );
		//Move to numbered folder.
		this.waitForFile( Pathutil.expanduser("~/Desktop/Certificates.p12") );
		
		File outputP12 = new File(this.data.getDataDir().getParentFile(), "Certificates.p12");
		Files.move( Pathutil.expanduser("~/Desktop/Certificates.p12"), outputP12 );
		Thread.sleep(1000);//wait for file copmlete - not sequence tag error?
		this.verifyP12( this.data, outputP12 );
	}

	private void uploadDownloadCert(File myCSR) throws Exception
	{
		this.waitFor(".formContent .file-input.validate");
		this.q(".formContent .file-input.validate").sendKeys(myCSR.getAbsolutePath());
		//sleep(WAITTIME)
		waitFor(".button.blue.submit");
		this.q(".button.blue.submit").click();
		try {
			this.waitFor("a[href*=\"/account/ios/certificate/downloadCer\"]");
		} catch (Exception nocert) {
			if (((JavascriptExecutor)this.browser).executeScript("return document.body.style.background").toString().equals( "red" ))
			{
				SendMailTLS.mail("Create-certificate encountered an unexpected error response. Sorry man.");
				//No telling when/if this will be fixed
				Thread.sleep(1000*60);
				createPushCert();
				return;
			} else {
				System.err.println((((JavascriptExecutor)this.browser).executeScript("return document.body.style.background").equals( "red" )));
			}
		}
		//Click download
		this.q(".formContent .row-details.push .button.small.blue").click();
		
		//#Now go download (automatically, into correct folder, as this has special Firefox-profile.)
		//#sleep(WAITTIME)
		//waitFor( CERTDOWNLOAD_LINK );
	}
	public void renewPushCert() throws Exception
	{
		this.startCert();
		//Due to keychain bug the old must be removed or it will keep exporting old cert.
		SikuliRunner.run( Pathutil.expanduser("~/Desktop/RemoveFromKeychain.sikuli") + " --args " + this.data.getId() );

		uploadDownloadCert( new File(data.getDataDir(), "CertificateSigningRequest.certSigningRequest") );
		File output = new File(data.getDataDir().getParentFile(), "aps.cer");
		this.waitForFile(output);
		Files.move( output, new File(this.data.getDataDir(), "aps.cer") );
		Pathutil.osOpen(new File(this.data.getDataDir(), "aps.cer") );
		
		Thread.sleep(2000);//Does this prevent invalid exported p12?
		
		SikuliRunner.run( Pathutil.expanduser("~/Desktop/ExportCertP12.sikuli") + " --args " + this.data.getId() );
		//Move to numbered folder.
		this.waitForFile( Pathutil.expanduser("~/Desktop/Certificates.p12") );
		this.verifyP12( this.data, Pathutil.expanduser("~/Desktop/Certificates.p12") );
		

		Files.move( new File(this.data.getDataDir(), "Certificates.p12"),
		            new File(this.data.getDataDir(), "Certificates-old.p12") );
		Files.move( Pathutil.expanduser("~/Desktop/Certificates.p12"),
				new File(this.data.getDataDir(), "Certificates.p12") );
	}
	
	public void createProvisioningProfiles() throws Exception
	{
		beLoggedIn();
		this.createProvisioningProfile(data, true);
		this.createProvisioningProfile(data, false);
	}

	/**
	 * Generates provisioning profile.
	 * @param data
	 * @param adHoc (true if adhoc not production)
	 * @return File output
	 * @throws Exception
	 */
	private File createProvisioningProfile( DirData data, boolean adHoc ) throws Exception
	{
		beLoggedIn();
		Thread.sleep(WAITTIME);
		this.browser.get("https://developer.apple.com/account/ios/profile/create");
		

		//Prevent collision in naming, and the "Multiple profiles found with the name..." error:
		DateFormat dateFormat = new SimpleDateFormat("MMdd ");
		String profileName = dateFormat.format(new Date());
		
		String name = data.getTitle();
		String bundleID = data.getId();
		String modesel = adHoc ? ".form input#type-adhoc" : ".form input#type-production";
		//Careful, the profileName has character limit:
		profileName += (adHoc ? "AH " + bundleID : bundleID);
		String provisionOutputFile = profileName.replace(' ', '_').replace(".", "") + ".mobileprovision";
		
		((JavascriptExecutor)this.browser).executeScript( // Make failure obvious when apple's glitching. See Apple bug report 20748381
				  "$( document ).ajaxComplete(function(evt, obj) { if(obj && obj.responseText && obj.responseText.indexOf('An unexpected error occurred. Please try again. ')>-1 ){ document.body.style.background = 'red'; } } )");
		
		this.browser.get("https://developer.apple.com/account/ios/profile/create");
		this.waitFor( modesel );
		this.q( modesel ).click();
		
		this.q(".blue.submit.button").click();
		Thread.sleep(WAITTIME);
		this.selectorSelect(bundleID, "[name=\"appIdId\"]");
		Thread.sleep(WAITTIME);
		this.q(".blue.button.submit").click();
		Thread.sleep(WAITTIME);
		this.waitFor("input[name=certificateIds]");
		this.q("input[name=certificateIds]").click();// #The SF cert. - currently 2016
		Thread.sleep(WAITTIME);
		this.q(".blue.button.submit").click();
		Thread.sleep(WAITTIME);
		if (adHoc)
		{
			this.q(".form .header .selectAll input").click();// #Select all devices for adhoc - unlike production mode
			Thread.sleep(WAITTIME);
			this.q(".blue.button.submit").click();
			Thread.sleep(WAITTIME);
		}
		
		this.waitFor( PROVISIONINGPROFILENAMEINPUTSEL );
		Thread.sleep(WAITTIME);
		this.q( PROVISIONINGPROFILENAMEINPUTSEL ).sendKeys( profileName );
		Thread.sleep(WAITTIME);
		//#unspecified error, need wait? this.q('.submit[role=button]').click()
		this.q(".blue.button.submit").click();
		Thread.sleep(WAITTIME);
		try //kinda odd "An unspecified error" box - works anyway
		{
			this.q(".ui-dialog-content.ui-widget-content .bottom-buttons >.ok.button").click();
			Thread.sleep(300);
			this.q(".blue.button.submit").click();
		}
		catch (ElementNotFoundException e)
		{ //pass
		}
		catch (NoSuchElementException e)
		{ //pass
		}
		
		try //Sometimes this gets stuck with HTTP 421.. whatever that means
		{
			this.waitFor(PROFILE_DOWNLOADER_LINK);
		} catch (Exception e)
		{
			Thread.sleep(1000*60); //and try again:
			return createProvisioningProfile(data, adHoc);
		}
		
		//Download:
		this.q(PROFILE_DOWNLOADER_LINK).click();
		Thread.sleep(WAITTIME);
		File myProvisioning = new File( data.getDataDir().getParentFile(), provisionOutputFile );
		Pathutil.osOpen(myProvisioning);
		return myProvisioning;
	}

	/**
	 * Selects the current app from dropdown select element
	 * @param bundleID
	 * @throws Exception 
	 */
	private void selectorSelect(String bundleID, String selectId) throws Exception
	{
		//TODO faster jQuery('[name=appIdId] option').each(function(){ if (this.innerHTML.indexOf("c4cc35")>-1 ) {jQuery(this).parent().val(this.value)} })
		boolean found = false;
		this.waitFor( selectId +" option");
		
		found = (boolean) ((JavascriptExecutor)browser).executeScript(
		 "success=false; jQuery('"+ selectId +" option').each(function(){"
			+ " if (this.innerHTML.indexOf(\""+ bundleID +"\")>-1 ) {jQuery(this).click(); jQuery(this).parent().val(this.value).trigger('change'); success=true;} "
		 + "}); return success;");

		//Selenium is Slooooow here:
		//WebElement appselector = this.q("select[name=appIdId]");
		/*for( WebElement element : appselector.findElements(By.tagName("option")))
		{
			String appid = StringUtils.strip( element.getText().trim(), ")" );
			String formid = element.getAttribute("value");
			if( appid.endsWith(bundleID))
			{
				element.click();
				found = true;
				return;
			}
		}*/
		if( !found )
		{
			throw new Exception("No selection for appid "+bundleID);
		}
	}
	
	/**
	 * Checks that the p12 is the same.
	 * @param data2 - an AppData object
	 * @param p12file - file of the p12 cert export for the app.
	 * @throws Exception on bad common-name/id value.
	 */
	public void verifyP12(DirData data2, File p12file) throws Exception
	{
		final String pw = ""; //no password
		//Basic p12 reader http://stackoverflow.com/questions/16970302
		KeyStore p12 = KeyStore.getInstance("pkcs12");
		p12.load(new FileInputStream(p12file), pw.toCharArray());
		Enumeration e = p12.aliases();
        while (e.hasMoreElements())
        {
            String alias = (String) e.nextElement();
            X509Certificate c = (X509Certificate) p12.getCertificate(alias);
            Principal subject = c.getSubjectDN();
            String subjectArray[] = subject.toString().split(",");
            if( c.getNotAfter().before( new Date() ) )
            {
            	throw new Exception( "Expired cert! " );
            }
            for (String s : subjectArray)
            {
                String[] str = s.trim().split("=");
                String key = str[0];
                String value = str[1];
                System.out.println(key + " - " + value);
                if (key.equals("UID") && !value.equals(data2.getId()))
                {
                	throw new Exception("Bad UID value " + value);
                }
            }
            if(data2.getTitle() != null && !alias.equalsIgnoreCase(data2.getTitle()))
            {
            	//TODO does this always happen for single-name here?
            	throw new Exception("Bad common-name "+alias);
            }
        }
	}
	
	public void close()
	{
		this.browser.close();
	}
	
	
}
