package main

import (
	// External
	werr "git.gibonuddevalla.se/go/wrappederror"
	"github.com/expr-lang/expr"
	"github.com/lib/pq"

	// Standard
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"
)

type Trigger struct {
	ID         int
	Name       string
	SectionID  int `db:"section_id"`
	Expression string
	Datapoints []string
}

func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{
	t.SectionID = sectionID
	t.Name = name
	t.Expression = "false"
	err = t.Update()
	return
} // }}}
func TriggersRetrieve() (areas []Area, err error) { // {{{
	areas = []Area{}

	row := service.Db.Conn.QueryRow(`
		WITH section_triggers AS (
			SELECT
				s.id AS id,
				s.area_id,
				s.name AS name,
				jsonb_agg(
					to_jsonb(t.*)
				) AS triggers
			FROM section s
			LEFT JOIN "trigger" t ON t.section_id = s.id
			GROUP BY
				s.id, s.name)
				
		SELECT
			 jsonb_agg(jsonsections)
		FROM (
			SELECT
				a.id,
				a.name,
				jsonb_agg(
					to_jsonb(
						s.*
					)
				) AS sections
			FROM area a
			LEFT JOIN section_triggers s ON s.area_id = a.id
			GROUP BY
				a.id, a.name
		) jsonsections
		`,
	)

	var jsonData []byte
	err = row.Scan(&jsonData)
	if err != nil {
		err = werr.Wrap(err)
		return
	}

	if jsonData == nil {
		return
	}

	err = json.Unmarshal(jsonData, &areas)
	if err != nil {
		err = werr.Wrap(err)
		return
	}

	return
} // }}}
func TriggersRetrieveByDatapoint(datapointName string) (triggers []Trigger, err error) { // {{{
	triggers = []Trigger{}
	row := service.Db.Conn.QueryRow(`
		SELECT jsonb_agg(t.*)
		FROM public."trigger" t
		WHERE
			datapoints @> $1
		`,
		fmt.Sprintf(`["%s"]`, datapointName),
	)

	var data []byte
	err = row.Scan(&data)
	if err != nil {
		err = werr.Wrap(err).WithData(datapointName)
		return
	}

	err = json.Unmarshal(data, &triggers)
	if err != nil {
		err = werr.Wrap(err).WithData(datapointName)
		return
	}

	return
} // }}}
func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{
	row := service.Db.Conn.QueryRow(`SELECT to_jsonb(t.*) FROM "trigger" t WHERE id=$1`, id)
	var jsonData []byte
	err = row.Scan(&jsonData)
	if err != nil {
		err = werr.Wrap(err)
		return
	}

	err = json.Unmarshal(jsonData, &trigger)
	return
} // }}}
func (t *Trigger) Validate() (ok bool, err error) { // {{{
	if strings.TrimSpace(t.Name) == "" {
		err = fmt.Errorf("Name can't be empty")
		return
	}

	if strings.TrimSpace(t.Expression) == "" {
		err = fmt.Errorf("Expression can't be empty")
		return
	}

	return true, nil
} // }}}
func (t *Trigger) Update() (err error) { // {{{
	var ok bool
	if ok, err = t.Validate(); !ok {
		return
	}

	if t.Datapoints == nil {
		t.Datapoints = []string{}
	}
	jsonDatapoints, _ := json.Marshal(t.Datapoints)
	if t.ID == 0 {
		var row *sql.Row
		row = service.Db.Conn.QueryRow(`
			INSERT INTO "trigger"(name, section_id, expression, datapoints)
			VALUES($1, $2, $3, $4)
			RETURNING id
			`,
			t.Name,
			t.SectionID,
			t.Expression,
			jsonDatapoints,
		)
		err = row.Scan(&t.ID)
		if err != nil {
			err = werr.Wrap(err).WithData(
				struct {
					SectionID      int
					Name           string
					Expression     string
					JsonDataPoints []byte
				}{
					t.SectionID,
					t.Name,
					t.Expression,
					jsonDatapoints,
				},
			)
			return
		}
	} else {
		_, err = service.Db.Conn.Exec(`
			UPDATE "trigger"
			SET
				name=$2,
				expression=$3,
				datapoints=$4
			WHERE
				id=$1
			`,
			t.ID,
			t.Name,
			t.Expression,
			jsonDatapoints,
		)
	}

	if pqErr, ok := err.(*pq.Error); ok {
		err = werr.Wrap(err).WithData(
			struct {
				Trigger      *Trigger
				PostgresCode pq.ErrorCode
				PostgresMsg  string
			}{
				t,
				pqErr.Code,
				pqErr.Code.Name(),
			})
	} else if err != nil {
		err = werr.Wrap(err).WithData(t)
	}
	return
} // }}}
func TriggerDelete(id int) (err error) { // {{{
	_, err = service.Db.Conn.Exec(`DELETE FROM public.trigger WHERE id=$1`, id)
	if err != nil {
		return werr.Wrap(err).WithData(id)
	}
	return
} // }}}

func (t *Trigger) Run() (output any, err error) { // {{{
	datapoints := make(map[string]Datapoint)
	for _, dpname := range t.Datapoints {
		var dp Datapoint
		dp, err = DatapointRetrieve(0, dpname)
		if err != nil {
			err = werr.Wrap(err)
			return
		}
		datapoints[dpname] = dp
	}

	env := make(map[string]any)
	for dpName, dp := range datapoints {
		env[dpName] = dp.LastDatapointValue.Value()
	}

	program, err := expr.Compile(t.Expression)
	if err != nil {
		return
	}

	output, err = expr.Run(program, env)
	if err != nil {
		return
	}
	return
} // }}}
