package auth

import (
	"code.secondbit.org/uuid.hg"

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

func (t Token) GetSQLTableName() string {
	return "tokens"
}

func (p *postgres) getTokenSQL(token string, refresh bool) *pan.Query {
	var t Token
	fields, _ := pan.GetFields(t)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(t))
	query.IncludeWhere()
	if !refresh {
		query.Include(pan.GetUnquotedColumn(t, "AccessToken")+" = ?", token)
	} else {
		query.Include(pan.GetUnquotedColumn(t, "RefreshToken")+" = ?", token)
	}
	return query.FlushExpressions(" ")
}

func (p *postgres) getToken(token string, refresh bool) (Token, error) {
	query := p.getTokenSQL(token, refresh)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return Token{}, err
	}
	var t Token
	var found bool
	for rows.Next() {
		err := pan.Unmarshal(rows, &t)
		if err != nil {
			return t, err
		}
		found = true
	}
	if err = rows.Err(); err != nil {
		return t, err
	}
	if !found {
		return t, ErrTokenNotFound
	}
	return t, nil
}

func (p *postgres) saveTokenSQL(token Token) *pan.Query {
	fields, values := pan.GetFields(token)
	query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(token))
	query.Include("(" + pan.QueryList(fields) + ")")
	query.Include("VALUES")
	query.Include("("+pan.VariableList(len(values))+")", values...)
	return query.FlushExpressions(" ")
}

func (p *postgres) saveToken(token Token) error {
	query := p.saveTokenSQL(token)
	_, err := p.db.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "tokens_pkey" {
		err = ErrTokenAlreadyExists
	}
	if err != nil || len(token.Scopes) < 1 {
		return err
	}
	return err
}

func (p *postgres) revokeTokenSQL(token string, refresh bool) *pan.Query {
	var t Token
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
	query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
	query.IncludeWhere()
	if !refresh {
		query.Include(pan.GetUnquotedColumn(t, "AccessToken")+" = ?", token)
	} else {
		query.Include(pan.GetUnquotedColumn(t, "RefreshToken")+" = ?", token)
	}
	return query.FlushExpressions(" ")
}

func (p *postgres) revokeToken(token string, refresh bool) error {
	query := p.revokeTokenSQL(token, refresh)
	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 == 0 {
		return ErrTokenNotFound
	}
	return nil
}

func (p *postgres) revokeTokensByProfileIDSQL(profileID uuid.ID) *pan.Query {
	var t Token
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
	query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(t, "ProfileID")+" = ?", profileID)
	return query.FlushExpressions(" ")
}

func (p *postgres) revokeTokensByProfileID(profileID uuid.ID) error {
	query := p.revokeTokensByProfileIDSQL(profileID)
	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 == 0 {
		return ErrProfileNotFound
	}
	return nil
}

func (p *postgres) revokeTokensByClientIDSQL(clientID uuid.ID) *pan.Query {
	var t Token
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
	query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(t, "ClientID")+" = ?", clientID)
	return query.FlushExpressions(" ")
}

func (p *postgres) revokeTokensByClientID(clientID uuid.ID) error {
	query := p.revokeTokensByClientIDSQL(clientID)
	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 == 0 {
		return ErrClientNotFound
	}
	return nil
}

func (p *postgres) getTokensByProfileIDSQL(profileID uuid.ID, num, offset int) *pan.Query {
	var token Token
	fields, _ := pan.GetFields(token)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(token))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(token, "ProfileID")+" = ?", profileID)
	query.IncludeLimit(int64(num))
	query.IncludeOffset(int64(offset))
	return query.FlushExpressions(" ")
}

func (p *postgres) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
	query := p.getTokensByProfileIDSQL(profileID, num, offset)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return []Token{}, err
	}
	var tokens []Token
	var tokenIDs []string
	for rows.Next() {
		var token Token
		err = pan.Unmarshal(rows, &token)
		if err != nil {
			return tokens, err
		}
		tokens = append(tokens, token)
		tokenIDs = append(tokenIDs, token.AccessToken)
	}
	if err = rows.Err(); err != nil {
		return tokens, err
	}
	return tokens, nil
}
