package db

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/go-gorp/gorp"

	"github.com/coreos/dex/connector"
	"github.com/coreos/dex/repo"
)

const (
	connectorConfigTableName = "connector_config"
)

func init() {
	register(table{
		name:    connectorConfigTableName,
		model:   connectorConfigModel{},
		autoinc: false,
		pkey:    []string{"id"},
	})
}

func newConnectorConfigModel(cfg connector.ConnectorConfig) (*connectorConfigModel, error) {
	b, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	m := &connectorConfigModel{
		ID:     cfg.ConnectorID(),
		Type:   cfg.ConnectorType(),
		Config: string(b),
	}

	return m, nil
}

type connectorConfigModel struct {
	ID     string `db:"id"`
	Type   string `db:"type"`
	Config string `db:"config"`
}

func (m *connectorConfigModel) ConnectorConfig() (connector.ConnectorConfig, error) {
	cfg, err := connector.NewConnectorConfigFromType(m.Type)
	if err != nil {
		return nil, err
	}

	if err = json.Unmarshal([]byte(m.Config), cfg); err != nil {
		return nil, err
	}

	return cfg, nil
}

func NewConnectorConfigRepo(dbm *gorp.DbMap) *ConnectorConfigRepo {
	return &ConnectorConfigRepo{dbMap: dbm}
}

type ConnectorConfigRepo struct {
	dbMap *gorp.DbMap
}

func (r *ConnectorConfigRepo) All() ([]connector.ConnectorConfig, error) {
	qt := r.dbMap.Dialect.QuotedTableForQuery("", connectorConfigTableName)
	q := fmt.Sprintf("SELECT * FROM %s", qt)
	objs, err := r.dbMap.Select(&connectorConfigModel{}, q)
	if err != nil {
		return nil, err
	}

	cfgs := make([]connector.ConnectorConfig, len(objs))
	for i, obj := range objs {
		m, ok := obj.(*connectorConfigModel)
		if !ok {
			return nil, errors.New("unable to cast connector to connectorConfigModel")
		}

		cfg, err := m.ConnectorConfig()
		if err != nil {
			return nil, err
		}
		cfgs[i] = cfg
	}

	return cfgs, nil
}

func (r *ConnectorConfigRepo) GetConnectorByID(tx repo.Transaction, id string) (connector.ConnectorConfig, error) {
	qt := r.dbMap.Dialect.QuotedTableForQuery("", connectorConfigTableName)
	q := fmt.Sprintf("SELECT * FROM %s WHERE id = $1", qt)
	var c connectorConfigModel
	if err := executor(r.dbMap, tx).SelectOne(&c, q, id); err != nil {
		if err == sql.ErrNoRows {
			return nil, connector.ErrorNotFound
		}
		return nil, err
	}
	return c.ConnectorConfig()
}

func (r *ConnectorConfigRepo) Set(cfgs []connector.ConnectorConfig) error {
	insert := make([]interface{}, len(cfgs))
	for i, cfg := range cfgs {
		m, err := newConnectorConfigModel(cfg)
		if err != nil {
			return err
		}

		insert[i] = m
	}

	tx, err := r.dbMap.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	qt := r.dbMap.Dialect.QuotedTableForQuery("", connectorConfigTableName)
	q := fmt.Sprintf("DELETE FROM %s", qt)
	if _, err = tx.Exec(q); err != nil {
		return err
	}

	if err = tx.Insert(insert...); err != nil {
		return fmt.Errorf("DB insert failed %#v: %v", insert, err)
	}

	return tx.Commit()
}