import re
import base64
import urllib
import logging

import oauth
import json

from google.appengine.ext import db
from google.appengine.api import urlfetch

from generic import TemplatePage

from bank import *

SERVER = 'demo.opensourcecurrency.org'
PORT = 80
REQUEST_TOKEN_URL = 'http://demo.opensourcecurrency.org/oauth/request_token'
ACCESS_TOKEN_URL = 'http://demo.opensourcecurrency.org/oauth/access_token'
AUTHORIZATION_URL = 'http://demo.opensourcecurrency.org/oauth/authorize'
CALLBACK_URL = 'http://www.austintimemachine.org/callback' # Replace this with your app callback URL
BASE = 'http://www.austintimemachine.org/' # Replace this with your app URL
RESOURCE_URL = 'http://demo.opensourcecurrency.org/people/<id>/exchanges' # Make an account and fill in your id
HOME_URL = 'http://demo.opensourcecurrency.org/'
CONSUMER_KEY = '' # Register your app, and fill this in
CONSUMER_SECRET = '' # Register your app, and fill this is

class MainPage(TemplatePage):
  def processContext(self, user, req, resp, context):
    pass

class TimeoutPage(TemplatePage):
  def processContext(self, user, req, resp, context):
    pass

class DonePage(TemplatePage):
  def processContext(self, user, req, resp, context):
    pass

class RequestToken(db.Model):
  user = db.UserProperty()
  oauth_key = db.StringProperty()
  oauth_secret = db.StringProperty()
  serial = db.StringProperty()

  def __repr__(self):
    return "<Token[%s, %s]>" % (self.oauth_key, self.oauth_secret)

  def getToken(self):
    return oauth.OAuthToken(self.oauth_key, self.oauth_secret)

class OAuthPage(TemplatePage):
  def authorize(self, user, callback, serial):
    authRedirect=self.fetchAuthorizer(user, callback, serial)
    if authRedirect:
      self.redirect(authRedirect)

  def fetchAuthorizer(self, user, callback, serial):
    # setup
    logging.info('** OAuth Python Library Example **')
    client = SimpleOAuthClient(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
    consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
    signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
    signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()

    # get request token
    logging.info('* Obtain a request token ...')
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, http_url=client.request_token_url)
#    oauth_request.sign_request(signature_method_plaintext, consumer, None)
    oauth_request.sign_request(signature_method_hmac_sha1, consumer, None)

    token = client.fetch_request_token(oauth_request)
    logging.info('key: %s' % str(token.key))
    logging.info('secret: %s' % str(token.secret))

    rt=RequestToken.get_or_insert("token:"+token.key, user=user, oauth_key=token.key, oauth_secret=token.secret, serial=serial)
    rt.put()

    logging.info('* Authorize the request token ...')
    oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, callback=callback, http_url=client.authorization_url)
    return oauth_request.to_url()

class WithdrawPage(OAuthPage):
  def processContext(self, user, req, resp, context):
    self.authorize(user, BASE+"withdrawCallback", "")

class DepositFormPage(OAuthPage):
  def processContext(self, user, req, resp, context):
    pass

class DepositPage(OAuthPage):
  def processContext(self, user, req, resp, context):
    serial=req.get('serial')
    context['serial']=serial
    logging.info("serial: "+str(serial))
    bill=verify(serial)
    logging.info("bill: "+str(bill))
    if bill:
      self.authorize(user, BASE+"depositCallback", serial)
    else:
      context['failure']=True

class CallbackPage(TemplatePage):
  def fetchAccess(user, oauth_key):
    logging.info("fetch.")
    client = SimpleOAuthClient(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
    consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
    signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
    signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()

    rt=RequestToken.get_by_key_name("token:"+oauth_key)
    logging.info("rt: "+str(rt))
    serial=rt.serial
    if serial=="":
      serial=None
    token=rt.getToken()
    logging.info("fetched token: "+str(rt)+" "+str(token))

    # get access token
    logging.info('* Obtain an access token ...')
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_url=client.access_token_url)
    oauth_request.sign_request(signature_method_hmac_sha1, consumer, token)
    token = client.fetch_access_token(oauth_request)
    logging.info('key: %s' % str(token.key))
    logging.info('secret: %s' % str(token.secret))
    return token, serial

  def getId(self, token):
    logging.info('* Access protected resources ...')

    client = SimpleOAuthClient(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
    consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
    signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
    signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()

    parameters = {
      'exchange': {'amount': '1'}, 'req': {'name': 'ATM Test'},
      'oauth_callback': CALLBACK_URL
    }
#    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='POST', http_url=RESOURCE_URL, parameters=parameters)
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='POST', http_url=RESOURCE_URL)
    oauth_request.sign_request(signature_method_hmac_sha1, consumer, token)

    return client.getId(oauth_request)

  def withdraw(self, token):
    logging.info('* Withdraw ...')

    client = SimpleOAuthClient(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
    consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
    signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()

    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='POST', http_url=RESOURCE_URL)
    oauth_request.sign_request(signature_method_hmac_sha1, consumer, token)

    return client.withdraw(oauth_request)

  def deposit(self, token, serial, id):
    logging.info('* Deposit ... '+str(serial)+" "+str(id))
#    authToken=self.getAuthenticityToken(id)
    success=self.doHttpAuthDeposit(id)
    if success:
      spend(serial)

  def getAuthenticityToken(self, id):
    logging.info('* Get Authenticity Token ...')

    headers={}
    headers['Authorization']='Basic '+base64.b64encode('email:password') # Fill in your email and password

    aresult=urlfetch.fetch(method=urlfetch.GET, url='http://demo.opensourcecurrency.org/people/'+str(id)+'/exchanges/new', follow_redirects=False)
    logging.info("get authToken: "+str(aresult.status_code))
    text=aresult.content
    logging.info("text: "+str(text))
    match=re.search('authenticity_token.*value="([^"]+)"', text)
    token=match.group(1)
    logging.info("auth token: "+str(token))
    return token

  def doHttpAuthDeposit(self, id):
    logging.info('* HTTP Auth Deposit ...')

    parameters = {'exchange': {'amount': '1'}, 'req': {'name': 'ATM Deposit'}}
    data=json.write(parameters)

    headers={}
    headers['Accept']='application/json'
    headers['Content-Type']='application/json'
    headers['Authorization']='Basic '+base64.b64encode('') # Fill in your email and password

    dresult=urlfetch.fetch(method=urlfetch.POST, url='http://demo.opensourcecurrency.org/people/'+str(id)+'/exchanges', headers=headers, payload=data, follow_redirects=False)
    logging.info("deposited: "+str(dresult.status_code))

class WithdrawCallbackPage(CallbackPage):
  def processContext(self, user, req, resp, context):
    logging.info('Withdraw Callback!')
    oauth_key=req.get('oauth_token')
    logging.info('oauth_key: '+str(oauth_key))
    accessToken, serial=self.fetchAccess(oauth_key)
    id=self.withdraw(accessToken)
    serial=mint(id, 1)
    logging.info("bill serial: "+str(serial))
    if not verify(serial):
      logging.info("Could not verify serial "+str(serial))
      test()
      context['failure']=True
    else:
      printBillUrl=BASE+'printBills?serial='+serial
      self.redirect(printBillUrl)

class PrintBillsPage(CallbackPage):
  def processContext(self, user, req, resp, context):
    logging.info('Print Bills!')
    serial=req.get('serial')
    logging.info("bill serial: "+str(serial))

    """
    if not verify(serial):
      logging.info("Could not verify serial "+str(serial))
      test()
      context['failure']=True
    else:
      context['serial']=serial
      depositUrl=BASE+'deposit?serial='+serial
      context['depositUrl']=depositUrl
      context['encodedUrl']=urllib.quote(depositUrl)
    """

    context['serial']=serial
    depositUrl=BASE+'deposit?serial='+serial
    context['depositUrl']=depositUrl
    context['encodedUrl']=urllib.quote(depositUrl)

class DepositCallbackPage(CallbackPage):
  def processContext(self, user, req, resp, context):
    logging.info('Deposit Callback!')
    oauth_key=req.get('oauth_token')
    logging.info('oauth_key: '+str(oauth_key))
    accessToken, serial=self.fetchAccess(oauth_key)
    context['serial']=serial
    if verify(serial):
      id = self.getId(accessToken)
      logging.info("id: "+str(id))
      self.deposit(accessToken, serial, id)
    else:
      context['failure']=True

class SimpleOAuthClient(oauth.OAuthClient):
    def __init__(self, request_token_url, access_token_url, authorization_url):
        self.request_token_url = request_token_url
        self.access_token_url = access_token_url
        self.authorization_url = authorization_url

    def fetch_request_token(self, oauth_request):
        result = urlfetch.fetch(url=self.request_token_url, headers=oauth_request.to_header())
        print 'result:', result.status_code
        s = result.content
        logging.info('s:'+str(s))
        return oauth.OAuthToken.from_string(s)

    def fetch_access_token(self, oauth_request):
        result = urlfetch.fetch(url=self.access_token_url, headers=oauth_request.to_header())
        print 'result:', result.status_code
        s = result.content
        logging.info('s:'+str(s))
        return oauth.OAuthToken.from_string(s)

    def access_resource(self, oauth_request):
        parameters = {'exchange': {'amount': '1'}, 'req': {'name': 'guitar lessons'}}
        data=json.write(parameters)

        headers=oauth_request.to_header()
        headers['Accept']='application/json'
        headers['Content-Type']='application/json'

        result = urlfetch.fetch(method=urlfetch.POST, url=RESOURCE_URL, headers=headers, payload=data)
        logging.info("result of access_resource: "+str(result.status_code))
        return result.content

    def getId(self, oauth_request):
        parameters = {'exchange': {'amount': '0.1'}, 'req': {'name': 'ATM Fee'}}
        data=json.write(parameters)

        headers=oauth_request.to_header()
        headers['Accept']='application/json'
        headers['Content-Type']='application/json'

        result = urlfetch.fetch(method=urlfetch.POST, url=RESOURCE_URL, headers=headers, payload=data)
        logging.info("result of access_resource: "+str(result.status_code))
        data=json.read(result.content)
        return data['exchange']['customer_id']

    def withdraw(self, oauth_request):
        parameters = {'exchange': {'amount': '1'}, 'req': {'name': 'ATM Withdrawl'}}
        data=json.write(parameters)

        headers=oauth_request.to_header()
        headers['Accept']='application/json'
        headers['Content-Type']='application/json'

        result = urlfetch.fetch(method=urlfetch.POST, url=RESOURCE_URL, headers=headers, payload=data)
        logging.info("result of access_resource: "+str(result.status_code))
        data=json.read(result.content)
        return data['exchange']['customer_id']
