package auth

import (
	"code.secondbit.org/uuid.hg"
	"github.com/lib/pq"
	"github.com/secondbit/pan"
)

func (c Client) GetSQLTableName() string {
	return "clients"
}

func (e Endpoint) GetSQLTableName() string {
	return "endpoints"
}

func (p *postgres) getClientSQL(id uuid.ID) *pan.Query {
	var client Client
	fields, _ := pan.GetFields(client)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(client))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(client, "ID")+" = ? AND "+pan.GetUnquotedColumn(client, "Deleted")+" = ?", id, false)
	return query.FlushExpressions(" ")
}

func (p *postgres) getClient(id uuid.ID) (Client, error) {
	query := p.getClientSQL(id)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return Client{}, err
	}
	var client Client
	var found bool
	for rows.Next() {
		err := pan.Unmarshal(rows, &client)
		if err != nil {
			return client, err
		}
		found = true
	}
	if err = rows.Err(); err != nil {
		return client, err
	}
	if !found {
		return client, ErrClientNotFound
	}
	return client, nil
}

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

func (p *postgres) saveClient(client Client) error {
	query := p.saveClientSQL(client)
	_, err := p.db.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "clients_pkey" {
		err = ErrClientAlreadyExists
	}
	return err
}

func (p *postgres) updateClientSQL(id uuid.ID, change ClientChange) *pan.Query {
	var client Client
	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(client)+" SET ")
	query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Secret")+" = ?", change.Secret)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "OwnerID")+" = ?", change.OwnerID)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Name")+" = ?", change.Name)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Logo")+" = ?", change.Logo)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Website")+" = ?", change.Website)
	query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Deleted")+" = ?", change.Deleted)
	query.FlushExpressions(", ")
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(client, "ID")+" = ?", id)
	return query.FlushExpressions(" ")
}

func (p *postgres) updateClient(id uuid.ID, change ClientChange) error {
	if change.Empty() {
		return nil
	}
	query := p.updateClientSQL(id, change)
	_, err := p.db.Exec(query.String(), query.Args...)
	return err
}

func (p *postgres) listClientsByOwnerSQL(ownerID uuid.ID, num, offset int) *pan.Query {
	var client Client
	fields, _ := pan.GetFields(client)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(client))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(client, "OwnerID")+" = ? AND "+pan.GetUnquotedColumn(client, "Deleted")+" = ?", ownerID, false)
	query.IncludeLimit(int64(num))
	query.IncludeOffset(int64(offset))
	return query.FlushExpressions(" ")
}

func (p *postgres) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
	query := p.listClientsByOwnerSQL(ownerID, num, offset)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return []Client{}, err
	}
	var clients []Client
	for rows.Next() {
		var client Client
		err = pan.Unmarshal(rows, &client)
		if err != nil {
			return clients, err
		}
		clients = append(clients, client)
	}
	if err = rows.Err(); err != nil {
		return clients, err
	}
	return clients, nil
}

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

func (p *postgres) addEndpoints(endpoints []Endpoint) error {
	if len(endpoints) < 1 {
		return nil
	}
	query := p.addEndpointsSQL(endpoints)
	_, err := p.db.Exec(query.String(), query.Args...)
	if e, ok := err.(*pq.Error); ok && e.Constraint == "endpoints_pkey" {
		return ErrEndpointAlreadyExists
	}
	return err
}

func (p *postgres) removeEndpointSQL(client, endpoint uuid.ID) *pan.Query {
	var e Endpoint
	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(e))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(e, "ID")+" = ? AND "+pan.GetUnquotedColumn(e, "ClientID")+" = ?", endpoint, client)
	return query.FlushExpressions(" ")
}

func (p *postgres) removeEndpoint(client, endpoint uuid.ID) error {
	query := p.removeEndpointSQL(client, endpoint)
	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 ErrEndpointNotFound
	}
	return nil
}

func (p *postgres) getEndpointSQL(client, endpoint uuid.ID) *pan.Query {
	var e Endpoint
	fields, _ := pan.GetFields(e)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(e))
	query.IncludeWhere()
	query.FlushExpressions(" ")
	query.Include(pan.GetUnquotedColumn(e, "ID")+" = ?", endpoint)
	query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client)
	return query.FlushExpressions(" AND ")
}

func (p *postgres) getEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
	query := p.getEndpointSQL(client, endpoint)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return Endpoint{}, err
	}
	var e Endpoint
	var found bool
	for rows.Next() {
		err := pan.Unmarshal(rows, &e)
		if err != nil {
			return e, err
		}
		found = true
	}
	if err = rows.Err(); err != nil {
		return e, err
	}
	if !found {
		return e, ErrEndpointNotFound
	}
	return e, nil
}

func (p *postgres) checkEndpointSQL(client uuid.ID, endpoint string) *pan.Query {
	var e Endpoint
	fields, _ := pan.GetFields(e)
	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(e))
	query.IncludeWhere()
	query.FlushExpressions(" ")
	query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client)
	query.Include(pan.GetUnquotedColumn(e, "NormalizedURI")+" = ?", endpoint)
	return query.FlushExpressions(" AND ")
}

func (p *postgres) checkEndpoint(client uuid.ID, endpoint string) (bool, error) {
	query := p.checkEndpointSQL(client, endpoint)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return false, err
	}
	var found bool
	for rows.Next() {
		found = true
	}
	if err = rows.Err(); err != nil {
		return found, err
	}
	return found, nil
}

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

func (p *postgres) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
	query := p.listEndpointsSQL(client, num, offset)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return []Endpoint{}, err
	}
	var endpoints []Endpoint
	for rows.Next() {
		var endpoint Endpoint
		err = pan.Unmarshal(rows, &endpoint)
		if err != nil {
			return endpoints, err
		}
		endpoints = append(endpoints, endpoint)
	}
	if err = rows.Err(); err != nil {
		return endpoints, err
	}
	return endpoints, nil
}

func (p *postgres) countEndpointsSQL(client uuid.ID) *pan.Query {
	var endpoint Endpoint
	query := pan.New(pan.POSTGRES, "SELECT COUNT(*) FROM "+pan.GetTableName(endpoint))
	query.IncludeWhere()
	query.Include(pan.GetUnquotedColumn(endpoint, "ClientID")+" = ?", client)
	return query.FlushExpressions(" ")
}

func (p *postgres) countEndpoints(client uuid.ID) (int64, error) {
	query := p.countEndpointsSQL(client)
	rows, err := p.db.Query(query.String(), query.Args...)
	if err != nil {
		return 0, err
	}
	var results int64
	for rows.Next() {
		err = pan.Unmarshal(rows, &results)
		if err != nil {
			return results, err
		}
	}
	if err = rows.Err(); err != nil {
		return results, err
	}
	return results, nil
}
