#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) 2014, NORDUnet A/S.
# See LICENSE for licensing information.

import urllib2
import urllib
import json
import base64
import sys
import struct
import hashlib
import itertools
from certtools import *

baseurl = "https://127.0.0.1:8080/"
certfiles = ["testcerts/cert1.txt", "testcerts/cert2.txt",
             "testcerts/cert3.txt", "testcerts/cert4.txt",
             "testcerts/cert5.txt"]

cc1 = get_certs_from_file(certfiles[0])
cc2 = get_certs_from_file(certfiles[1])
cc3 = get_certs_from_file(certfiles[2])
cc4 = get_certs_from_file(certfiles[3])
cc5 = get_certs_from_file(certfiles[4])

failures = 0
indentation = ""

def testgroup(name):
    global indentation
    print name + ":"
    indentation = "    "

def print_error(message, *args):
    global failures, indentation
    print indentation + "ERROR:", message % args
    failures += 1

def print_success(message, *args):
    print indentation + message % args

def assert_equal(actual, expected, name, quiet=False, nodata=False):
    global failures
    if actual != expected:
        if nodata:
            print_error("%s differs", name)
        else:
            print_error("%s expected %s got %s", name, expected, actual)
    elif not quiet:
        print_success("%s was correct", name)

def print_and_check_tree_size(expected):
    global failures
    sth = get_sth(baseurl)
    try:
        check_sth_signature(baseurl, sth)
    except AssertionError, e:
        print_error("%s", e)
    except ecdsa.keys.BadSignatureError, e:
        print_error("bad STH signature")
    tree_size = sth["tree_size"]
    assert_equal(tree_size, expected, "tree size")

def do_add_chain(chain):
    global failures
    try:
        result = add_chain(baseurl, {"chain":map(base64.b64encode, chain)})
    except ValueError, e:
        print_error("%s", e)
    try:
        check_sct_signature(baseurl, chain[0], result)
    except AssertionError, e:
        print_error("%s", e)
    except ecdsa.keys.BadSignatureError, e:
        print_error("bad SCT signature")
    print_success("signature check succeeded")
    return result

def get_and_validate_proof(timestamp, chain, leaf_index, nentries):
    cert = chain[0]
    merkle_tree_leaf = pack_mtl(timestamp, cert)
    leaf_hash = get_leaf_hash(merkle_tree_leaf)
    sth = get_sth(baseurl)
    proof = get_proof_by_hash(baseurl, leaf_hash, sth["tree_size"])
    leaf_index = proof["leaf_index"]
    inclusion_proof = [base64.b64decode(e) for e in proof["audit_path"]]
    assert_equal(leaf_index, leaf_index, "leaf_index")
    assert_equal(len(inclusion_proof), nentries, "audit_path length")

    calc_root_hash = verify_inclusion_proof(inclusion_proof, leaf_index, sth["tree_size"], leaf_hash)
    root_hash = base64.b64decode(sth["sha256_root_hash"])

    assert_equal(root_hash, calc_root_hash, "verified root hash", nodata=True)
    get_and_check_entry(timestamp, chain, leaf_index)

def get_and_validate_consistency_proof(sth1, sth2, size1, size2):
    consistency_proof = [base64.decodestring(entry) for entry in get_consistency_proof(baseurl, size1, size2)]
    (old_treehead, new_treehead) = verify_consistency_proof(consistency_proof, size1, size2, sth1)
    #print repr(sth1), repr(old_treehead)
    #print repr(sth2), repr(new_treehead)
    assert_equal(old_treehead, sth1, "sth1", nodata=True)
    assert_equal(new_treehead, sth2, "sth2", nodata=True)


def get_and_check_entry(timestamp, chain, leaf_index):
    entries = get_entries(baseurl, leaf_index, leaf_index)
    assert_equal(len(entries), 1, "get_entries", quiet=True)
    fetched_entry = entries["entries"][0]
    merkle_tree_leaf = pack_mtl(timestamp, chain[0])
    leaf_input =  base64.decodestring(fetched_entry["leaf_input"])
    assert_equal(leaf_input, merkle_tree_leaf, "entry", nodata=True)
    extra_data = base64.decodestring(fetched_entry["extra_data"])
    certchain = decode_certificate_chain(extra_data)

    submittedcertchain = chain[1:]

    for (submittedcert, fetchedcert, i) in zip(submittedcertchain,
                                               certchain, itertools.count(1)):
        assert_equal(fetchedcert, submittedcert, "cert %d in chain" % (i,))

    if len(certchain) == len(submittedcertchain) + 1:
        last_issuer = get_cert_info(submittedcertchain[-1])["issuer"]
        root_subject = get_cert_info(certchain[-1])["subject"]
        if last_issuer == root_subject:
            print_success("fetched chain has an appended root cert")
        else:
            print_error("fetched chain has an extra entry")
    elif len(certchain) == len(submittedcertchain):
        print_success("cert chains are the same length")
    else:
        print_error("cert chain length %d expected %d or %d",
                    len(certchain),
                    len(submittedcertchain),
                    len(submittedcertchain))

def merge():
    return subprocess.call(["./merge.py", "--baseurl", "https://127.0.0.1:8080/", "--frontend", "https://127.0.0.1:8082/", "--storage", "https://127.0.0.1:8081/", "--mergedb", "../rel/mergedb", "--signing", "https://127.0.0.1:8088/", "--own-keyname", "merge-1", "--own-keyfile", "../rel/privatekeys/merge-1-private.pem"])

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

print_and_check_tree_size(0)

testgroup("cert1")

result1 = do_add_chain(cc1)

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

size_sth = {}

print_and_check_tree_size(1)
size_sth[1] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"])

result2 = do_add_chain(cc1)

assert_equal(result2["timestamp"], result1["timestamp"], "timestamp")

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

print_and_check_tree_size(1)
size1_v2_sth = base64.b64decode(get_sth(baseurl)["sha256_root_hash"])

assert_equal(size_sth[1], size1_v2_sth, "sth", nodata=True)

# TODO: add invalid cert and check that it generates an error
#       and that treesize still is 1

get_and_validate_proof(result1["timestamp"], cc1, 0, 0)

testgroup("cert2")

result3 = do_add_chain(cc2)

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

print_and_check_tree_size(2)
size_sth[2] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"])

get_and_validate_proof(result1["timestamp"], cc1, 0, 1)
get_and_validate_proof(result3["timestamp"], cc2, 1, 1)

testgroup("cert3")

result4 = do_add_chain(cc3)

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

print_and_check_tree_size(3)
size_sth[3] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"])

get_and_validate_proof(result1["timestamp"], cc1, 0, 2)
get_and_validate_proof(result3["timestamp"], cc2, 1, 2)
get_and_validate_proof(result4["timestamp"], cc3, 2, 1)

testgroup("cert4")

result5 = do_add_chain(cc4)

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

print_and_check_tree_size(4)
size_sth[4] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"])

get_and_validate_proof(result1["timestamp"], cc1, 0, 2)
get_and_validate_proof(result3["timestamp"], cc2, 1, 2)
get_and_validate_proof(result4["timestamp"], cc3, 2, 2)
get_and_validate_proof(result5["timestamp"], cc4, 3, 2)

testgroup("cert5")

result6 = do_add_chain(cc5)

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

print_and_check_tree_size(5)
size_sth[5] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"])

get_and_validate_proof(result1["timestamp"], cc1, 0, 3)
get_and_validate_proof(result3["timestamp"], cc2, 1, 3)
get_and_validate_proof(result4["timestamp"], cc3, 2, 3)
get_and_validate_proof(result5["timestamp"], cc4, 3, 3)
get_and_validate_proof(result6["timestamp"], cc5, 4, 1)

mergeresult = merge()
assert_equal(mergeresult, 0, "merge", quiet=True)

for first_size in range(1, 5):
    for second_size in range(first_size + 1, 6):
        get_and_validate_consistency_proof(size_sth[first_size], size_sth[second_size], first_size, second_size)

print "-------"
if failures:
    print failures, "failed tests" if failures != 1 else "failed test"
    sys.exit(1)
else:
    print "all tests succeeded"