package store

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/pingcap/test-infra/services/tcms/service"
	serror "github.com/pingcap/test-infra/services/tcms/service/common/errors"
	"github.com/pingcap/test-infra/services/tcms/utils"

	"github.com/doug-martin/goqu/v9"
	"github.com/jmoiron/sqlx"

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/common/model/tcms/taskruns"
	handler "github.com/pingcap/test-infra/services/tcms/service/store/svctaskrunhandler"
	"github.com/pingcap/test-infra/services/tcms/utils/sql"
)

const SvcTaskRunTableName = "svc_task_runs"

var svcTaskRunFields = []string{
	"create_at", "update_at", "create_by_id", "update_by_id", "spec_svc_task_name",
	"spec_inputs", "status_completed", "status_message", "status_outputs", "status_implementation",
}
var svc_task_run_insert_query string
var svc_task_run_update_query string

func init() {
	fieldStr := strings.Join(svcTaskRunFields, ", ")
	var holders []string
	for _, field := range svcTaskRunFields {
		holders = append(holders, ":"+field)
	}
	holderStr := strings.Join(holders, ",")
	svc_task_run_insert_query = fmt.Sprintf("insert into %s (%s) values (%s)", SvcTaskRunTableName, fieldStr, holderStr)

	var mutations []string
	for _, field := range svcTaskRunFields {
		if field == "create_at" || field == "create_by_id" {
			continue
		}
		mutations = append(mutations, fmt.Sprintf("%s = :%s", field, field))
	}
	mutateStr := strings.Join(mutations, ",")
	svc_task_run_update_query = fmt.Sprintf("update %s set %s where id = :id", SvcTaskRunTableName, mutateStr)
}

type svcTaskRunService struct {
	db      *sql.Database
	handler handler.SvcRunHandler
	now     func() time.Time
}

func (r *svcTaskRunService) List(ctx context.Context, opts taskruns.ListSvcTaskRunOpts) (*taskruns.SvcTaskRunList, error) {
	rt := &taskruns.SvcTaskRunList{}
	ct := utils.GetUtilContext(ctx)
	stmt := sql.Builder.From(SvcTaskRunTableName)

	stmt = stmt.Select(fields2interfaces(svcTaskRunFields)...).
		Prepared(true).
		Order(goqu.I("id").Desc()).
		Offset(opts.Offset).
		Limit(opts.Count)
	if opts.Completed != nil {
		stmt = stmt.Where(goqu.I("status_completed").In(*opts.Completed))
	}
	query, args, err := stmt.ToSQL()
	if err != nil {
		return nil, err
	}
	var dbos []SvcTaskRunDBO
	if err := r.db.SelectContext(ctx, &dbos, query, args...); err != nil {
		ct.Log().Errorf(err, "list cases failed")
		return nil, err
	}
	rt.Items = make([]taskruns.SvcTaskRun, 0, len(dbos))
	for _, dbo := range dbos {
		apiObj, err := dbo.To()
		if err != nil {
			return nil, err
		}
		rt.Items = append(rt.Items, *apiObj)
	}
	rt.FillHeader()
	return rt, nil
}

func (r *svcTaskRunService) Get(ctx context.Context, id model.ID, opts taskruns.GetSvcTaskRunOpts) (*taskruns.SvcTaskRun, error) {
	obj, err := getSvcTaskRunByID(ctx, id, r.db)
	if err != nil && sql.IsErrNoRows(err) {
		return nil, serror.New(http.StatusBadRequest, err)
	}
	return obj, err
}

func (r *svcTaskRunService) Sync(ctx context.Context, id model.ID) (*taskruns.SvcTaskRun, error) {
	obj, err := getSvcTaskRunByID(ctx, id, r.db)
	if err != nil && sql.IsErrNoRows(err) {
		return nil, serror.New(http.StatusBadRequest, err)
	}
	obj, err = r.handler.OnSync(ctx, obj)
	if err != nil {
		return nil, err
	}
	obj, err = r.UpdateStatus(ctx, *obj, taskruns.UpdateSvcTaskRunOpts{})
	if err != nil {
		return nil, err
	}
	return obj, err
}

func (r *svcTaskRunService) UpdateStatus(ctx context.Context, obj taskruns.SvcTaskRun, opts taskruns.UpdateSvcTaskRunOpts) (*taskruns.SvcTaskRun, error) {
	var rt *taskruns.SvcTaskRun
	err := r.db.Transaction(ctx, func(tx *sqlx.Tx) error {
		old, err := getSvcTaskRunByID(ctx, obj.MetaData.ID, tx)
		if err != nil {
			return serror.New(400, err)
		}
		old.Status = obj.Status
		old.MetaData.UpdateAt = r.now()
		if user := utils.GetUtilContext(ctx).User(); user != nil {
			obj.MetaData.UpdateByID = user.ID
		}
		dbo := &SvcTaskRunDBO{}
		err = dbo.From(*old)
		if err != nil {
			return err
		}
		updated, err := tx.NamedExecContext(ctx, svc_task_run_update_query, dbo)
		if err != nil {
			return err
		}
		rows, err := updated.RowsAffected()
		if err != nil {
			return err
		}
		if rows != 1 {
			return fmt.Errorf("no such rows")
		}
		obj, err := getSvcTaskRunByID(ctx, obj.MetaData.ID, tx)
		if err != nil {
			return err
		}
		rt = obj
		return nil
	})
	if err != nil {
		return nil, err
	}
	rt.FillHeader()
	return rt, nil
}

func (r *svcTaskRunService) Create(ctx context.Context, obj taskruns.SvcTaskRun, opts taskruns.CreateSvcTaskRunOpts) (*taskruns.SvcTaskRun, error) {
	now := r.now()
	obj.MetaData.CreateAt = now
	obj.MetaData.UpdateAt = now
	var userID model.ID
	if user := utils.GetUtilContext(ctx).User(); user != nil {
		obj.MetaData.UpdateByID = user.ID
	}
	obj.MetaData.UpdateByID = userID
	obj.MetaData.CreateByID = userID
	var rt *taskruns.SvcTaskRun
	if obj.MetaData.ID != 0 {
		return nil, fmt.Errorf("ID must not be provided")
	}
	err := r.db.Transaction(ctx, func(tx *sqlx.Tx) error {
		_, err := r.handler.OnCreate(handler.WrapSqlTx(ctx, tx), &obj)
		if err != nil {
			return err
		}
		dbo := &SvcTaskRunDBO{}
		err = dbo.From(obj)
		if err != nil {
			return err
		}
		inserted, err := tx.NamedExecContext(ctx, svc_task_run_insert_query, dbo)
		if err != nil {
			return err
		}
		id, err := inserted.LastInsertId()
		if err != nil {
			return err
		}
		obj, err := getSvcTaskRunByID(ctx, model.ID(id), tx)
		if err != nil {
			return err
		}
		rt = obj
		return nil
	})
	if err != nil {
		return nil, err
	}
	return rt, nil
}

func (r *svcTaskRunService) Delete(ctx context.Context, id model.ID, opts taskruns.DeleteSvcTaskRunOpts) (rt *taskruns.SvcTaskRun, err error) {
	err = r.db.Transaction(ctx, func(tx *sqlx.Tx) error {
		rt, err = getSvcTaskRunByID(ctx, id, tx)
		if err != nil {
			return err
		}
		_, err := tx.ExecContext(ctx, "delete from "+SvcTaskRunTableName+" where id = ?", id)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return
}

func getSvcTaskRunByID(ctx context.Context, id model.ID, q sqlx.QueryerContext) (rt *taskruns.SvcTaskRun, err error) {
	dbo := SvcTaskRunDBO{}
	err = sqlx.GetContext(ctx, q, &dbo, "SELECT * FROM "+SvcTaskRunTableName+" WHERE id = ?", int(id))
	if err != nil {
		return
	}
	return dbo.To()
}

func NewSvcTaskRunService(db *sql.Database, service service.SvcTaskService, planExecutionService *planExecutionService, baseURL string) *svcTaskRunService {
	return &svcTaskRunService{db: db, now: time.Now, handler: handler.NewMuxHandler(service, planExecutionService, baseURL)}
}
