import stripe as _stripe
_stripe.api_version = '2016-06-15'

import logging
import time
import sys
import csv
import codecs

from itertools import groupby
from datetime import datetime, timedelta, date
from cStringIO import StringIO

from app import billing as stripe


def _format_timestamp(stripe_timestamp):
  date_obj = date.fromtimestamp(stripe_timestamp)
  return date_obj.strftime('%m/%d/%Y')


def _format_money(stripe_money):
  return stripe_money/100.0


def list_charges(num_days):
  """ List all charges that have occurred in the past specified number of days.
  """
  now = datetime.utcnow()
  starting_from = now - timedelta(days=num_days)
  starting_timestamp = str(int(time.mktime(starting_from.timetuple())))
  created = {'gte': starting_timestamp}
  starting_after_kw = {}

  while True:
    charges = stripe.Charge.list(limit=100, expand=['data.invoice'], created=created,
                                 **starting_after_kw)
    for charge in charges.data:
      yield charge

    if not charges.has_more:
      break

    starting_after_kw = {'starting_after': charge.id}


def _date_key(line_item):
  return line_item.start, line_item.end


def expand_invoice(invoice, total_amount):
  if invoice is None:
    yield total_amount, None, None, None
  else:
    data_iter = groupby(invoice.lines.data, lambda li: (li.period.start, li.period.end))
    for (period_start, period_end), line_items_iter in data_iter:
      line_items = list(line_items_iter)
      period_amount = sum(line_item.amount for line_item in line_items)
      yield period_amount, period_start, period_end, line_items[-1].plan


def format_charge(charge):
  """ Generator which will return one or more line items corresponding to the
      line items for this charge.
      """
  ch_status = 'Paid'
  if charge.failure_code is not None:
    ch_status = 'Failed'
  elif charge.amount_refunded > 0:
    ch_status = 'Refunded'

  card = charge.source

  invoice_iterable = expand_invoice(charge.invoice, charge.amount)
  for line_amount, period_start, period_end, plan in invoice_iterable:
    yield [
      _format_timestamp(charge.created),
      _format_timestamp(period_start) if period_start is not None else None,
      _format_timestamp(period_end) if period_end is not None else None,
      _format_money(line_amount),
      'credit_card',
      ch_status,
      plan.name if plan is not None else None,
      charge.id,
      charge.customer,
      card.address_city,
      card.address_state,
      card.address_country,
      card.address_zip,
      card.country,
    ]


class _UnicodeWriter(object):
  """
  A CSV writer which will write rows to CSV file "f",
  which is encoded in the given encoding.
  """
  def __init__(self, f, dialect=csv.excel, encoding='utf-8', **kwds):
    # Redirect output to a queue
    self.queue = StringIO()
    self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
    self.stream = f
    self.encoder = codecs.getincrementalencoder(encoding)()

  @staticmethod
  def _encode_cell(cell):
    if cell is None:
      return cell
    return unicode(cell).encode('utf-8')

  def writerow(self, row):
    self.writer.writerow([self._encode_cell(s) for s in row])
    # Fetch UTF-8 output from the queue ...
    data = self.queue.getvalue()
    data = data.decode('utf-8')
    # ... and reencode it into the target encoding
    data = self.encoder.encode(data)
    # write to the target stream
    self.stream.write(data)
    # empty queue
    self.queue.truncate(0)


if __name__ == '__main__':
  logging.basicConfig(level=logging.WARN)

  days = 30
  if len(sys.argv) > 1:
    days = int(sys.argv[1])

  transaction_writer = _UnicodeWriter(sys.stdout)
  rows = (line_item
          for one_charge in list_charges(days)
          for line_item in format_charge(one_charge))
  for row in rows:
    transaction_writer.writerow(row)
    sys.stdout.flush()