package main

import (
	// External
	werr "git.ahall.se/go/wrappederror"

	// Standard
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"time"
)

type Problem struct { // {{{
	ID              int
	Start           time.Time
	End             time.Time
	Acknowledged    bool
	Datapoints      map[string]any
	DatapointValues map[string]any `json:"datapoints"`
	TriggerID       int            `json:"trigger_id"`
	TriggerName     string         `json:"trigger_name"`
	AreaName        string         `json:"area_name"`
	SectionName     string         `json:"section_name"`
} // }}}

func ProblemsRetrieve(showCurrent bool, from, to time.Time) (problems []Problem, err error) { // {{{
	problems = []Problem{}
	row := db.QueryRow(
		context.Background(),
		`
		SELECT
			jsonb_agg(problems.*)
		FROM (
			(SELECT
				p.id,
				p.start,
				TO_CHAR(p.end, 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM') AS end,
				p.acknowledged,
				p.datapoints,
				t.id   AS trigger_id,
				p.trigger_name AS trigger_name,
				a.name AS area_name,
				s.name AS section_name
			FROM problem p
			LEFT JOIN "trigger" t ON p.trigger_id = t.id
			LEFT JOIN section s ON t.section_id = s.id
			LEFT JOIN area a ON s.area_id = a.id
			WHERE
				CASE
					WHEN NOT $1 THEN p.end IS NULL
					WHEN $1 THEN
						p.start >= $2 AND
						(
							p.end IS NULL OR
							p.end <= $3
						)
				END

			ORDER BY
				p.start DESC)

			UNION ALL

			(SELECT
				-1 AS id,
				dp.last_value,
				null,
				false,
				'{}',
				-1 AS trigger_id,
				CONCAT(
					'NODATA: ',
					dp.name
				) AS trigger_name,
				'[NODATA]' AS area_name,
				'[NODATA]' AS section_name
			FROM datapoint dp
			WHERE
				dp.nodata_is_problem
			ORDER BY
				dp.name ASC)
		) AS problems`,
		showCurrent,
		from,
		to,
	)

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

	if jsonBody == nil {
		return
	}

	err = json.Unmarshal(jsonBody, &problems)
	if err != nil {
		err = werr.Wrap(err)
	}
	return
} // }}}
func ProblemStart(trigger Trigger) (problemID int, err error) { // {{{
	row := db.QueryRow(
		context.Background(),
		`
		SELECT COUNT(id)
		FROM problem
		WHERE
			trigger_id = $1 AND
			"end" IS NULL
		GROUP BY trigger_id
		`,
		trigger.ID,
	)
	var openProblems int
	err = row.Scan(&openProblems)
	if err != nil && err != sql.ErrNoRows {
		err = werr.Wrap(err).WithData(trigger.ID)
		return
	}

	// Open up a new problem if no open exists.
	if openProblems == 0 {
		datapointValuesJson, _ := json.Marshal(trigger.DatapointValues)
		row = db.QueryRow(
			context.Background(),
			`INSERT INTO problem(trigger_id, trigger_name, datapoints, trigger_expression) VALUES($1, $2, $3, $4) RETURNING id`,
			trigger.ID,
			trigger.Name,
			datapointValuesJson,
			trigger.Expression,
		)
		err = row.Scan(&problemID)
		if err != nil {
			err = werr.Wrap(err).WithData(trigger)
		}
	}
	return
} // }}}
func ProblemClose(trigger Trigger) (problemID int, err error) { // {{{
	row := db.QueryRow(
		context.Background(),
		`UPDATE problem SET "end"=NOW() WHERE trigger_id=$1 AND "end" IS NULL RETURNING id`,
		trigger.ID,
	)

	err = row.Scan(&problemID)
	if err == sql.ErrNoRows {
		err = nil
		return
	}

	if err != nil {
		err = werr.Wrap(err).WithData(trigger)
		return
	}
	return
} // }}}
func ProblemAcknowledge(id int, state bool) (err error) { // {{{
	_, err = db.Exec(context.Background(), `UPDATE problem SET "acknowledged"=$2 WHERE id=$1`, id, state)
	if err != nil {
		err = werr.Wrap(err).WithData(id)
		return
	}
	return
} // }}}

func (p Problem) FormattedValues() string { // {{{
	out := []string{}
	for key, val := range p.DatapointValues {
		var keyval string

		switch val.(type) {
		case int:
			keyval = fmt.Sprintf("%s: %d", key, val)

		case string:
			if str, ok := val.(string); ok {
				timeVal, err := time.Parse(time.RFC3339, str)
				if err == nil {
					formattedTime := timeVal.Format("2006-01-02 15:04:05")
					keyval = fmt.Sprintf("%s: %s", key, formattedTime)
				} else {
					keyval = fmt.Sprintf("%s: %s", key, val)
				}
			}

		default:
			keyval = fmt.Sprintf("%s: %v", key, val)
		}

		out = append(out, keyval)

	}
	sort.Strings(out)

	return strings.Join(out, "\n")
} // }}}
func (p Problem) IsArchived() bool { // {{{
	return !p.End.IsZero()
} // }}}
