package auth

import (
	"time"

	"code.secondbit.org/uuid.hg"

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

func (s Session) GetSQLTableName() string {
	return "sessions"
}

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

func (p *postgres) createSession(session Session) error {
	query := p.createSessionSQL(session)
	_, err := p.db.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "sessions_pkey" {
		err = ErrSessionAlreadyExists
	}
	return err
}

func (p *postgres) getSessionSQL(id string) *pan.Query {
	var session Session
	fields, _ := pan.GetFields(session)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(session))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(session, "ID")+" = ?", id)
	return query.FlushExpressions(" ")
}

func (p *postgres) getSession(id string) (Session, error) {
	query := p.getSessionSQL(id)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return Session{}, err
	}
	var session Session
	var found bool
	for rows.Next() {
		err := pan.Unmarshal(rows, &session)
		if err != nil {
			return session, err
		}
		found = true
	}
	if err = rows.Err(); err != nil {
		return session, err
	}
	if !found {
		return session, ErrSessionNotFound
	}
	return session, nil
}

func (p *postgres) terminateSessionSQL(id string) *pan.Query {
	var session Session
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(session)+" SET")
	query.Include(pan.GetUnquotedColumn(session, "Active")+" = ?", false)
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(session, "ID")+" = ?", id)
	return query.FlushExpressions(" ")
}

func (p *postgres) terminateSession(id string) error {
	query := p.terminateSessionSQL(id)
	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 ErrSessionNotFound
	}
	return nil
}

func (p *postgres) removeSessionSQL(id string) *pan.Query {
	var session Session
	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(session))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(session, "ID")+" = ?", id)
	return query.FlushExpressions(" ")
}

func (p *postgres) removeSession(id string) error {
	query := p.removeSessionSQL(id)
	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 ErrSessionNotFound
	}
	return nil
}

func (p *postgres) listSessionsSQL(profile uuid.ID, before time.Time, num int64) *pan.Query {
	var session Session
	fields, _ := pan.GetFields(session)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(session))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(session, "ProfileID")+" = ?", profile)
	if !before.IsZero() {
		query.Include(pan.GetUnquotedColumn(session, "Created")+" < ?", before)
	}
	query.FlushExpressions(" AND ")
	if num > 0 {
		query.IncludeLimit(num)
	}
	return query.FlushExpressions(" ")
}

func (p *postgres) listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
	query := p.listSessionsSQL(profile, before, num)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return []Session{}, err
	}
	var sessions []Session
	for rows.Next() {
		var session Session
		err := pan.Unmarshal(rows, &session)
		if err != nil {
			return sessions, err
		}
		sessions = append(sessions, session)
	}
	if err = rows.Err(); err != nil {
		return sessions, err
	}
	return sessions, nil
}
