package auth

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

type authCodeScope struct {
	Code  string
	Scope string
}

func (acs authCodeScope) GetSQLTableName() string {
	return "authorization_codes_scopes"
}

func (ac AuthorizationCode) GetSQLTableName() string {
	return "authorization_codes"
}

func (p *postgres) getAuthorizationCodeSQL(code string) *pan.Query {
	var ac AuthorizationCode
	fields, _ := pan.GetFields(ac)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(ac))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(ac, "Code")+" = ?", code)
	return query.FlushExpressions(" ")
}

func (p *postgres) getAuthorizationCodeScopesSQL(codes []string) *pan.Query {
	var acs authCodeScope
	fields, _ := pan.GetFields(acs)
	codesI := make([]interface{}, len(codes))
	for pos, code := range codes {
		codesI[pos] = code
	}
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(acs))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(acs, "Code")+" IN ("+pan.VariableList(len(codesI))+")", codesI...)
	return query.FlushExpressions(" ")
}

func (p *postgres) getAuthorizationCode(code string) (AuthorizationCode, error) {
	query := p.getAuthorizationCodeSQL(code)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return AuthorizationCode{}, err
	}
	var ac AuthorizationCode
	var found bool
	for rows.Next() {
		err := pan.Unmarshal(rows, &ac)
		if err != nil {
			return ac, err
		}
		found = true
	}
	if err = rows.Err(); err != nil {
		return ac, err
	}
	if !found {
		return ac, ErrAuthorizationCodeNotFound
	}
	query = p.getAuthorizationCodeScopesSQL([]string{code})
	rows, err = p.db.Query(query.String(), query.Args...)
	if err != nil {
		return ac, err
	}
	for rows.Next() {
		var acs authCodeScope
		err = pan.Unmarshal(rows, &acs)
		if err != nil {
			return ac, err
		}
		ac.Scopes = append(ac.Scopes, acs.Scope)
	}
	if err = rows.Err(); err != nil {
		return ac, err
	}
	return ac, nil
}

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

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

func (p *postgres) saveAuthorizationCode(authCode AuthorizationCode) error {
	query := p.saveAuthorizationCodeSQL(authCode)
	_, err := p.db.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "authorization_codes_pkey" {
		err = ErrAuthorizationCodeAlreadyExists
	}
	if err != nil || len(authCode.Scopes) < 1 {
		return err
	}
	var acs []authCodeScope
	for _, scope := range authCode.Scopes {
		acs = append(acs, authCodeScope{Code: authCode.Code, Scope: scope})
	}
	query = p.saveAuthorizationCodeScopesSQL(acs)
	_, err = p.db.Exec(query.String(), query.Args...)
	return err
}

func (p *postgres) deleteAuthorizationCodeSQL(code string) *pan.Query {
	var authCode AuthorizationCode
	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(authCode))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(authCode, "Code")+" = ?", code)
	return query.FlushExpressions(" ")
}

func (p *postgres) deleteAuthorizationCodeScopesSQL(code string) *pan.Query {
	var acs authCodeScope
	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(acs))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(acs, "Code")+" = ?", code)
	return query.FlushExpressions(" ")
}

func (p *postgres) deleteAuthorizationCode(code string) error {
	query := p.deleteAuthorizationCodeSQL(code)
	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 ErrAuthorizationCodeNotFound
	}
	query = p.deleteAuthorizationCodeScopesSQL(code)
	_, err = p.db.Exec(query.String(), query.Args...)
	return err
}

func (p *postgres) useAuthorizationCodeSQL(code string) *pan.Query {
	var authCode AuthorizationCode
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(authCode)+" SET ")
	query.Include(pan.GetUnquotedColumn(authCode, "Used")+" = ?", true)
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(authCode, "Code")+" = ?", code)
	return query.FlushExpressions(" ")
}

func (p *postgres) useAuthorizationCode(code string) error {
	query := p.useAuthorizationCodeSQL(code)
	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 ErrAuthorizationCodeNotFound
	}
	return nil
}
