package legacy

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/grafana/grafana/pkg/storage/legacysql"
	"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)

type dashboardStars struct {
	OrgID   int64
	UserUID string
	First   int64
	Last    int64

	Dashboards []string
}

type LegacySQL struct {
	db      legacysql.LegacyDatabaseProvider
	startup time.Time
}

func NewLegacySQL(db legacysql.LegacyDatabaseProvider) *LegacySQL {
	return &LegacySQL{db: db, startup: time.Now()}
}

// NOTE: this does not support paging -- lets check if that will be a problem in cloud
func (s *LegacySQL) getDashboardStars(ctx context.Context, orgId int64, user string) ([]dashboardStars, int64, error) {
	var max sql.NullString
	sql, err := s.db(ctx)
	if err != nil {
		return nil, 0, err
	}

	req := newStarQueryReq(sql, user, orgId)

	q, err := sqltemplate.Execute(sqlDashboardStarsQuery, req)
	if err != nil {
		return nil, 0, fmt.Errorf("execute template %q: %w", sqlDashboardStarsQuery.Name(), err)
	}

	sess := sql.DB.GetSqlxSession()
	rows, err := sess.Query(ctx, q, req.GetArgs()...)
	if err != nil {
		return nil, 0, err
	}
	defer func() {
		if rows != nil {
			_ = rows.Close()
		}
	}()

	stars := []dashboardStars{}
	current := &dashboardStars{}
	var orgID int64
	var userUID string
	var dashboardUID string
	var updated time.Time

	for rows.Next() {
		err := rows.Scan(&orgID, &userUID, &dashboardUID, &updated)
		if err != nil {
			return nil, 0, err
		}

		if orgID != current.OrgID || userUID != current.UserUID {
			if current.UserUID != "" {
				stars = append(stars, *current)
			}
			current = &dashboardStars{
				OrgID:   orgID,
				UserUID: userUID,
			}
		}
		ts := updated.UnixMilli()
		if ts > current.Last {
			current.Last = ts
		}
		if ts < current.First || current.First == 0 {
			current.First = ts
		}
		current.Dashboards = append(current.Dashboards, dashboardUID)
	}

	// Add the last value
	if current.UserUID != "" {
		stars = append(stars, *current)
	}

	// Find the RV unless it is a user query
	if userUID == "" {
		req.Reset()
		q, err = sqltemplate.Execute(sqlDashboardStarsRV, req)
		if err != nil {
			return nil, 0, fmt.Errorf("execute template %q: %w", sqlDashboardStarsRV.Name(), err)
		}
		err = sess.Get(ctx, &max, q)
		if err != nil {
			return nil, 0, fmt.Errorf("unable to get RV %w", err)
		}
		if max.Valid && max.String != "" {
			t, _ := time.Parse(time.RFC3339, max.String)
			if !t.IsZero() {
				updated = t
			}
		} else {
			updated = s.startup
		}
	}

	return stars, updated.UnixMilli(), err
}

func (s *LegacySQL) GetMaxTime(ctx context.Context) (time.Time, error) {
	var max sql.NullString
	sql, err := s.db(ctx)
	if err != nil {
		return time.Time{}, err
	}

	req := newStarQueryReq(sql, "", 0)

	q, err := sqltemplate.Execute(sqlDashboardStarsRV, req)
	if err != nil {
		return time.Time{}, fmt.Errorf("execute template %q: %w", sqlDashboardStarsRV.Name(), err)
	}

	sess := sql.DB.GetSqlxSession()
	err = sess.Get(ctx, &max, q)
	if err != nil {
		return time.Time{}, fmt.Errorf("unable to get RV %w", err)
	}
	if max.Valid && max.String != "" {
		t, _ := time.Parse(time.RFC3339, max.String)
		if !t.IsZero() {
			return t, nil
		}
	}
	return time.Now(), nil
}

func (s *LegacySQL) ListUsers(ctx context.Context, orgId int64) ([]string, error) {
	sql, err := s.db(ctx)
	if err != nil {
		return nil, err
	}

	req := newStarQueryReq(sql, "", orgId)

	q, err := sqltemplate.Execute(sqlListUsersWithStars, req)
	if err != nil {
		return nil, fmt.Errorf("execute template %q: %w", sqlListUsersWithStars.Name(), err)
	}

	var users []string
	sess := sql.DB.GetSqlxSession()
	err = sess.Select(ctx, &users, q, req.GetArgs()...)
	if err != nil {
		return nil, fmt.Errorf("unable to list users with stars %w", err)
	}
	return users, nil
}
