package subscriptions

import (
	"log"

	"code.secondbit.org/uuid.hg"

	"github.com/lib/pq"
	"github.com/secondbit/pan"
)

// GetSQLTableName fulfills the pan.SQLTableNamer interface, allowing
// us to manipulate Subscriptions with pan.
func (s Subscription) GetSQLTableName() string {
	return "subscriptions"
}

func (p Postgres) resetSQL() *pan.Query {
	var subscription Subscription
	query := pan.New(pan.POSTGRES, "TRUNCATE "+pan.GetTableName(subscription))
	return query.FlushExpressions(" ")
}

func (p Postgres) reset() error {
	query := p.resetSQL()
	_, err := p.Exec(query.String(), query.Args...)
	if err != nil {
		return err
	}
	return nil
}

func (p Postgres) createSubscriptionSQL(subscription Subscription) *pan.Query {
	fields, values := pan.GetFields(subscription)
	query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(subscription))
	query.Include("(" + pan.QueryList(fields) + ")")
	query.Include("VALUES")
	query.Include("("+pan.VariableList(len(values))+")", values...)
	return query.FlushExpressions(" ")
}

func (p Postgres) createSubscription(sub Subscription) error {
	query := p.createSubscriptionSQL(sub)
	_, err := p.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "subscriptions_pkey" {
		err = ErrSubscriptionAlreadyExists
	} else if e, ok := err.(*pq.Error); ok && e.Constraint == "subscriptions_stripe_subscription_key" {
		err = ErrStripeSubscriptionAlreadyExists
	}
	return err
}

func (p Postgres) updateSubscriptionSQL(id uuid.ID, change SubscriptionChange) *pan.Query {
	var subscription Subscription
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(subscription)+" SET")
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "StripeSubscription")+" = ?", change.StripeSubscription)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "Plan")+" = ?", change.Plan)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "Status")+" = ?", change.Status)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "Canceling")+" = ?", change.Canceling)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "TrialStart")+" = ?", change.TrialStart)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "TrialEnd")+" = ?", change.TrialEnd)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "PeriodStart")+" = ?", change.PeriodStart)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "PeriodEnd")+" = ?", change.PeriodEnd)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "CanceledAt")+" = ?", change.CanceledAt)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "FailedChargeAttempts")+" = ?", change.FailedChargeAttempts)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "LastFailedCharge")+" = ?", change.LastFailedCharge)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(subscription, "LastNotified")+" = ?", change.LastNotified)
	query.FlushExpressions(", ")
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(subscription, "UserID")+" = ?", id)
	return query.FlushExpressions(" ")
}

func (p Postgres) updateSubscription(id uuid.ID, change SubscriptionChange) error {
	if change.IsEmpty() {
		return ErrSubscriptionChangeEmpty
	}

	query := p.updateSubscriptionSQL(id, change)
	res, err := p.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "subscriptions_stripe_subscription_key" {
		return ErrStripeSubscriptionAlreadyExists
	} else if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows < 1 {
		return ErrSubscriptionNotFound
	}
	return nil
}

func (p Postgres) deleteSubscriptionSQL(id uuid.ID) *pan.Query {
	var subscription Subscription
	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(subscription))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(subscription, "UserID")+" = ?", id)
	return query.FlushExpressions(" ")
}

func (p Postgres) deleteSubscription(id uuid.ID) error {
	query := p.deleteSubscriptionSQL(id)
	res, err := p.Exec(query.String(), query.Args...)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows < 1 {
		return ErrSubscriptionNotFound
	}
	return nil
}

func (p Postgres) getSubscriptionsSQL(ids []uuid.ID) *pan.Query {
	var subscription Subscription
	fields, _ := pan.GetFields(subscription)
	intIDs := make([]interface{}, len(ids))
	for pos, id := range ids {
		intIDs[pos] = id
	}
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(subscription))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(subscription, "UserID") + " IN")
	query.Include("("+pan.VariableList(len(intIDs))+")", intIDs...)
	return query.FlushExpressions(" ")
}

func (p Postgres) getSubscriptions(ids []uuid.ID) (map[string]Subscription, error) {
	results := map[string]Subscription{}
	if len(ids) < 1 {
		return results, ErrNoSubscriptionID
	}
	query := p.getSubscriptionsSQL(ids)
	rows, err := p.Query(query.String(), query.Args...)
	if err != nil {
		return results, err
	}
	for rows.Next() {
		var subscription Subscription
		err := pan.Unmarshal(rows, &subscription)
		if err != nil {
			return results, err
		}
		results[subscription.UserID.String()] = subscription
	}
	if err := rows.Err(); err != nil {
		return results, err
	}
	return results, nil
}

func (p Postgres) getSubscriptionStatsCountSQL() *pan.Query {
	var subscription Subscription
	query := pan.New(pan.POSTGRES, "SELECT COUNT(*) FROM")
	query.Include(pan.GetTableName(subscription))
	return query.FlushExpressions(" ")
}

func (p Postgres) getSubscriptionStatsCancelingSQL() *pan.Query {
	var subscription Subscription
	query := pan.New(pan.POSTGRES, "SELECT COUNT(*) FROM")
	query.Include(pan.GetTableName(subscription))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(subscription, "Canceling")+" = ?", true)
	return query.FlushExpressions(" ")
}

func (p Postgres) getSubscriptionStatsFailingSQL() *pan.Query {
	var subscription Subscription
	query := pan.New(pan.POSTGRES, "SELECT COUNT(*) FROM")
	query.Include(pan.GetTableName(subscription))
	query.IncludeWhere()
	statuses := []interface{}{"past_due", "unpaid"}
	query.Include(pan.GetUnquotedColumn(subscription, "Status")+" IN ("+pan.VariableList(len(statuses))+")", statuses...)
	return query.FlushExpressions(" ")
}

func (p Postgres) getSubscriptionStatsPlansSQL() *pan.Query {
	var subscription Subscription
	fields := []interface{}{pan.GetUnquotedColumn(subscription, "Plan"), "COUNT(*)"}
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM")
	query.Include(pan.GetTableName(subscription))
	query.Include("GROUP BY " + pan.GetUnquotedColumn(subscription, "Plan"))
	return query.FlushExpressions(" ")
}

func (p Postgres) getSubscriptionStats() (SubscriptionStats, error) {
	stats := SubscriptionStats{
		Plans: map[string]int64{},
	}
	query := p.getSubscriptionStatsCountSQL()
	err := p.QueryRow(query.String(), query.Args...).Scan(&stats.Number)
	if err != nil {
		log.Printf("Error querying for total subscriptions: %+v\n", err)
		return stats, err
	}
	query = p.getSubscriptionStatsCancelingSQL()
	err = p.QueryRow(query.String(), query.Args...).Scan(&stats.Canceling)
	if err != nil {
		log.Printf("Error querying for canceling subscriptions: %+v\n", err)
		return stats, err
	}
	query = p.getSubscriptionStatsFailingSQL()
	err = p.QueryRow(query.String(), query.Args...).Scan(&stats.Failing)
	if err != nil {
		log.Printf("Error querying for failing subscriptions: %+v\n", err)
		return stats, err
	}
	query = p.getSubscriptionStatsPlansSQL()
	rows, err := p.Query(query.String(), query.Args...)
	if err != nil {
		log.Printf("Error querying for plans: %+v\n", err)
		return stats, err
	}
	for rows.Next() {
		var plan string
		var count int64
		err := rows.Scan(&plan, &count)
		if err != nil {
			log.Printf("Error scanning database row for plans: %+v\n", err)
			continue
		}
		stats.Plans[plan] = count
	}
	if err := rows.Err(); err != nil {
		log.Printf("Error querying for plans: %+v\n", err)
		return stats, err
	}
	return stats, nil
}
