package scopes

import (
	"code.secondbit.org/scopes.hg/types"
	"golang.org/x/net/context"

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

func (p *postgres) createScopesSQL(scopes []scopeTypes.Scope) *pan.Query {
	fields, _ := pan.GetFields(scopes[0])
	query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(scopes[0]))
	query.Include("(" + pan.QueryList(fields) + ")")
	query.Include("VALUES")
	query.FlushExpressions(" ")
	for _, scope := range scopes {
		_, values := pan.GetFields(scope)
		query.Include("("+pan.VariableList(len(values))+")", values...)
	}
	return query.FlushExpressions(", ")
}

func (p *postgres) CreateScopes(scopes []scopeTypes.Scope, ctx context.Context) error {
	if len(scopes) < 1 {
		return nil
	}
	query := p.createScopesSQL(scopes)
	_, err := p.db.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "scopes_pkey" {
		// BUG(paddy): we need to parse e.Detail to pull out the duplicate Scope ID
		err = ErrScopeAlreadyExists(e.Detail)
	}
	return err
}

func (p *postgres) getScopesSQL(ids []string) *pan.Query {
	var scope scopeTypes.Scope
	intids := make([]interface{}, len(ids))
	for pos, id := range ids {
		intids[pos] = id
	}
	fields, _ := pan.GetFields(scope)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
	query.Include("("+pan.VariableList(len(ids))+")", intids...)
	return query.FlushExpressions(" ")
}

func (p *postgres) GetScopes(ids []string, ctx context.Context) (map[string]scopeTypes.Scope, error) {
	query := p.getScopesSQL(ids)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return map[string]scopeTypes.Scope{}, err
	}
	scopes := map[string]scopeTypes.Scope{}
	for rows.Next() {
		var scope scopeTypes.Scope
		err := pan.Unmarshal(rows, &scope)
		if err != nil {
			return scopes, err
		}
		scopes[scope.ID] = scope
	}
	if err = rows.Err(); err != nil {
		return scopes, err
	}
	return scopes, nil
}

func (p *postgres) updateScopeSQL(change scopeTypes.ScopeChange) *pan.Query {
	var scope scopeTypes.Scope
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(scope)+" SET ")
	query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Name")+" = ?", change.Name)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Description")+" = ?", change.Description)
	query.FlushExpressions(", ")
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(scope, "ID")+" = ?", change.ScopeID)
	return query.FlushExpressions(" ")
}

func (p *postgres) UpdateScope(change scopeTypes.ScopeChange, ctx context.Context) error {
	if change.Empty() {
		return nil
	}
	query := p.updateScopeSQL(change)
	res, err := p.db.Exec(query.String(), query.Args...)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows < 1 {
		return ErrScopeNotFound
	}
	return err
}

func (p *postgres) removeScopesSQL(ids []string) *pan.Query {
	var scope scopeTypes.Scope
	intids := make([]interface{}, len(ids))
	for pos, id := range ids {
		intids[pos] = id
	}
	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(scope))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
	query.Include("("+pan.VariableList(len(ids))+")", intids...)
	return query.FlushExpressions(" ")
}

func (p *postgres) RemoveScopes(ids []string, ctx context.Context) error {
	query := p.removeScopesSQL(ids)
	res, err := p.db.Exec(query.String(), query.Args...)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows < 1 {
		return ErrScopeNotFound
	}
	return nil
}

func (p *postgres) listScopesSQL() *pan.Query {
	var scope scopeTypes.Scope
	fields, _ := pan.GetFields(scope)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
	return query.FlushExpressions(" ")
}

func (p *postgres) ListScopes(ctx context.Context) ([]scopeTypes.Scope, error) {
	query := p.listScopesSQL()
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return []scopeTypes.Scope{}, err
	}
	var scopes []scopeTypes.Scope
	for rows.Next() {
		var scope scopeTypes.Scope
		err = pan.Unmarshal(rows, &scope)
		if err != nil {
			return scopes, err
		}
		scopes = append(scopes, scope)
	}
	if err = rows.Err(); err != nil {
		return scopes, err
	}
	return scopes, nil
}
