import stripe

from flask import request
from app import billing
from endpoints.api import (resource, nickname, ApiResource, validate_json_request, log_action,
                           related_user_resource, internal_only, Unauthorized, NotFound,
                           require_user_admin, show_if, hide_if, abort)
from endpoints.api.subscribe import subscribe, subscription_view
from auth.permissions import AdministerOrganizationPermission
from auth.auth_context import get_authenticated_user
from data import model
from data.billing import PLANS

import features

def carderror_response(e):
  return {'carderror': e.message}, 402


def get_card(user):
  card_info = {
    'is_valid': False
  }

  if user.stripe_id:
    try:
      cus = billing.Customer.retrieve(user.stripe_id)
    except stripe.APIConnectionError as e:
      abort(503, message='Cannot contact Stripe')

    if cus and cus.default_card:
      # Find the default card.
      default_card = None
      for card in cus.cards.data:
        if card.id == cus.default_card:
          default_card = card
          break

      if default_card:
        card_info = {
          'owner': default_card.name,
          'type': default_card.type,
          'last4': default_card.last4,
          'exp_month': default_card.exp_month,
          'exp_year': default_card.exp_year
        }

  return {'card': card_info}


def set_card(user, token):
  if user.stripe_id:
    try:
      cus = billing.Customer.retrieve(user.stripe_id)
    except stripe.APIConnectionError as e:
      abort(503, message='Cannot contact Stripe')

    if cus:
      try:
        cus.card = token
        cus.save()
      except stripe.CardError as exc:
        return carderror_response(exc)
      except stripe.InvalidRequestError as exc:
        return carderror_response(exc)
      except stripe.APIConnectionError as e:
        return carderror_response(e)

  return get_card(user)


def get_invoices(customer_id):
  def invoice_view(i):
    return {
      'id': i.id,
      'date': i.date,
      'period_start': i.period_start,
      'period_end': i.period_end,
      'paid': i.paid,
      'amount_due': i.amount_due,
      'next_payment_attempt': i.next_payment_attempt,
      'attempted': i.attempted,
      'closed': i.closed,
      'total': i.total,
      'plan': i.lines.data[0].plan.id if i.lines.data[0].plan else None
    }
    
  try:
    invoices = billing.Invoice.all(customer=customer_id, count=12)
  except stripe.APIConnectionError as e:
    abort(503, message='Cannot contact Stripe')

  return {
    'invoices': [invoice_view(i) for i in invoices.data]
  }


@resource('/v1/plans/')
@show_if(features.BILLING)
class ListPlans(ApiResource):
  """ Resource for listing the available plans. """
  @nickname('listPlans')
  def get(self):
    """ List the avaialble plans. """
    return {
      'plans': PLANS,
    }


@resource('/v1/user/card')
@internal_only
@show_if(features.BILLING)
class UserCard(ApiResource):
  """ Resource for managing a user's credit card. """
  schemas = {
    'UserCard': {
      'id': 'UserCard',
      'type': 'object',
      'description': 'Description of a user card',
      'required': [
        'token',
      ],
      'properties': {
        'token': {
          'type': 'string',
          'description': 'Stripe token that is generated by stripe checkout.js',
        },
      },
    },
  }

  @require_user_admin
  @nickname('getUserCard')
  def get(self):
    """ Get the user's credit card. """
    user = get_authenticated_user()
    return get_card(user)

  @require_user_admin
  @nickname('setUserCard')
  @validate_json_request('UserCard')
  def post(self):
    """ Update the user's credit card. """
    user = get_authenticated_user()
    token = request.get_json()['token']
    response = set_card(user, token)
    log_action('account_change_cc', user.username)
    return response


@resource('/v1/organization/<orgname>/card')
@internal_only
@related_user_resource(UserCard)
@show_if(features.BILLING)
class OrganizationCard(ApiResource):
  """ Resource for managing an organization's credit card. """
  schemas = {
    'OrgCard': {
      'id': 'OrgCard',
      'type': 'object',
      'description': 'Description of a user card',
      'required': [
        'token',
      ],
      'properties': {
        'token': {
          'type': 'string',
          'description': 'Stripe token that is generated by stripe checkout.js',
        },
      },
    },
  }

  @nickname('getOrgCard')
  def get(self, orgname):
    """ Get the organization's credit card. """
    permission = AdministerOrganizationPermission(orgname)
    if permission.can():
      organization = model.get_organization(orgname)
      return get_card(organization)

    raise Unauthorized()

  @nickname('setOrgCard')
  @validate_json_request('OrgCard')
  def post(self, orgname):
    """ Update the orgnaization's credit card. """
    permission = AdministerOrganizationPermission(orgname)
    if permission.can():
      organization = model.get_organization(orgname)
      token = request.get_json()['token']
      response = set_card(organization, token)
      log_action('account_change_cc', orgname)
      return response

    raise Unauthorized()


@resource('/v1/user/plan')
@internal_only
@show_if(features.BILLING)
class UserPlan(ApiResource):
  """ Resource for managing a user's subscription. """
  schemas = {
    'UserSubscription': {
      'id': 'UserSubscription',
      'type': 'object',
      'description': 'Description of a user card',
      'required': [
        'plan',
      ],
      'properties': {
        'token': {
          'type': 'string',
          'description': 'Stripe token that is generated by stripe checkout.js',
        },
        'plan': {
          'type': 'string',
          'description': 'Plan name to which the user wants to subscribe',
        },
      },
    },
  }

  @require_user_admin
  @nickname('updateUserSubscription')
  @validate_json_request('UserSubscription')
  def put(self):
    """ Create or update the user's subscription. """
    request_data = request.get_json()
    plan = request_data['plan']
    token = request_data['token'] if 'token' in request_data else None
    user = get_authenticated_user()
    return subscribe(user, plan, token, False)  # Business features not required

  @require_user_admin
  @nickname('getUserSubscription')
  def get(self):
    """ Fetch any existing subscription for the user. """
    cus = None
    user = get_authenticated_user()
    private_repos = model.get_private_repo_count(user.username)

    if user.stripe_id:
      try:
        cus = billing.Customer.retrieve(user.stripe_id)
      except stripe.APIConnectionError as e:
        abort(503, message='Cannot contact Stripe')

      if cus.subscription:                                
        return subscription_view(cus.subscription, private_repos)

    return {
      'hasSubscription': False,
      'isExistingCustomer': cus is not None,
      'plan': 'free',
      'usedPrivateRepos': private_repos,
    }


@resource('/v1/organization/<orgname>/plan')
@internal_only
@related_user_resource(UserPlan)
@show_if(features.BILLING)
class OrganizationPlan(ApiResource):
  """ Resource for managing a org's subscription. """
  schemas = {
    'OrgSubscription': {
      'id': 'OrgSubscription',
      'type': 'object',
      'description': 'Description of a user card',
      'required': [
        'plan',
      ],
      'properties': {
        'token': {
          'type': 'string',
          'description': 'Stripe token that is generated by stripe checkout.js',
        },
        'plan': {
          'type': 'string',
          'description': 'Plan name to which the user wants to subscribe',
        },
      },
    },
  }

  @nickname('updateOrgSubscription')
  @validate_json_request('OrgSubscription')
  def put(self, orgname):
    """ Create or update the org's subscription. """
    permission = AdministerOrganizationPermission(orgname)
    if permission.can():
      request_data = request.get_json()
      plan = request_data['plan']
      token = request_data['token'] if 'token' in request_data else None
      organization = model.get_organization(orgname)
      return subscribe(organization, plan, token, True)  # Business plan required

    raise Unauthorized()

  @nickname('getOrgSubscription')
  def get(self, orgname):
    """ Fetch any existing subscription for the org. """
    cus = None
    permission = AdministerOrganizationPermission(orgname)
    if permission.can():
      private_repos = model.get_private_repo_count(orgname)
      organization = model.get_organization(orgname)
      if organization.stripe_id:
        try:
          cus = billing.Customer.retrieve(organization.stripe_id)
        except stripe.APIConnectionError as e:
          abort(503, message='Cannot contact Stripe')

        if cus.subscription:                                
          return subscription_view(cus.subscription, private_repos)

      return {
        'hasSubscription': False,
        'isExistingCustomer': cus is not None,
        'plan': 'free',
        'usedPrivateRepos': private_repos,
      }

    raise Unauthorized()


@resource('/v1/user/invoices')
@internal_only
@show_if(features.BILLING)
class UserInvoiceList(ApiResource):
  """ Resource for listing a user's invoices. """
  @require_user_admin
  @nickname('listUserInvoices')
  def get(self):
    """ List the invoices for the current user. """
    user = get_authenticated_user()
    if not user.stripe_id:
      raise NotFound()
        
    return get_invoices(user.stripe_id)


@resource('/v1/organization/<orgname>/invoices')
@internal_only
@related_user_resource(UserInvoiceList)
@show_if(features.BILLING)
class OrgnaizationInvoiceList(ApiResource):
  """ Resource for listing an orgnaization's invoices. """
  @nickname('listOrgInvoices')
  def get(self, orgname):
    """ List the invoices for the specified orgnaization. """
    permission = AdministerOrganizationPermission(orgname)
    if permission.can():
      organization = model.get_organization(orgname)
      if not organization.stripe_id:
        raise NotFound()
        
      return get_invoices(organization.stripe_id)

    raise Unauthorized()