package legacy

import (
	"context"
	"errors"
	"fmt"
	"time"

	claims "github.com/grafana/authlib/types"
	"github.com/grafana/grafana/pkg/registry/apis/iam/common"
	"github.com/grafana/grafana/pkg/storage/legacysql"
	"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)

type GetServiceAccountInternalIDQuery struct {
	OrgID int64
	UID   string
}

type GetServiceAccountInternalIDResult struct {
	ID int64
}

var sqlQueryServiceAccountInternalIDTemplate = mustTemplate("service_account_internal_id.sql")

func newGetServiceAccountInternalID(sql *legacysql.LegacyDatabaseHelper, q *GetServiceAccountInternalIDQuery) getServiceAccountInternalIDQuery {
	return getServiceAccountInternalIDQuery{
		SQLTemplate:  sqltemplate.New(sql.DialectForDriver()),
		UserTable:    sql.Table("user"),
		OrgUserTable: sql.Table("org_user"),
		Query:        q,
	}
}

type getServiceAccountInternalIDQuery struct {
	sqltemplate.SQLTemplate
	UserTable    string
	OrgUserTable string
	Query        *GetServiceAccountInternalIDQuery
}

func (r getServiceAccountInternalIDQuery) Validate() error {
	return nil // TODO
}

func (s *legacySQLStore) GetServiceAccountInternalID(
	ctx context.Context,
	ns claims.NamespaceInfo,
	query GetServiceAccountInternalIDQuery,
) (*GetServiceAccountInternalIDResult, error) {
	query.OrgID = ns.OrgID
	if query.OrgID == 0 {
		return nil, fmt.Errorf("expected non zero org id")
	}

	sql, err := s.sql(ctx)
	if err != nil {
		return nil, err
	}

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

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

	if err != nil {
		return nil, err
	}

	if !rows.Next() {
		return nil, errors.New("service account not found")
	}

	var id int64
	if err := rows.Scan(&id); err != nil {
		return nil, err
	}

	return &GetServiceAccountInternalIDResult{
		id,
	}, nil
}

type ListServiceAccountsQuery struct {
	UID        string
	OrgID      int64
	Pagination common.Pagination
}

type ListServiceAccountResult struct {
	Items    []ServiceAccount
	Continue int64
	RV       int64
}

type ServiceAccount struct {
	ID       int64
	UID      string
	Name     string
	Disabled bool
	Created  time.Time
	Updated  time.Time
}

var sqlQueryServiceAccountsTemplate = mustTemplate("service_accounts_query.sql")

func newListServiceAccounts(sql *legacysql.LegacyDatabaseHelper, q *ListServiceAccountsQuery) listServiceAccountsQuery {
	return listServiceAccountsQuery{
		SQLTemplate:  sqltemplate.New(sql.DialectForDriver()),
		UserTable:    sql.Table("user"),
		OrgUserTable: sql.Table("org_user"),
		Query:        q,
	}
}

type listServiceAccountsQuery struct {
	sqltemplate.SQLTemplate
	Query        *ListServiceAccountsQuery
	UserTable    string
	OrgUserTable string
}

func (r listServiceAccountsQuery) Validate() error {
	return nil // TODO
}

func (s *legacySQLStore) ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error) {
	// for continue
	query.Pagination.Limit += 1
	query.OrgID = ns.OrgID
	if ns.OrgID == 0 {
		return nil, fmt.Errorf("expected non zero orgID")
	}

	sql, err := s.sql(ctx)
	if err != nil {
		return nil, err
	}

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

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

	res := &ListServiceAccountResult{}
	if err != nil {
		return nil, err
	}

	var lastID int64
	for rows.Next() {
		var s ServiceAccount
		err := rows.Scan(&s.ID, &s.UID, &s.Name, &s.Disabled, &s.Created, &s.Updated)
		if err != nil {
			return res, err
		}

		lastID = s.ID
		res.Items = append(res.Items, s)
		if len(res.Items) > int(query.Pagination.Limit)-1 {
			res.Items = res.Items[0 : len(res.Items)-1]
			res.Continue = lastID
			break
		}
	}

	if query.UID == "" {
		// FIXME: we need to filer for service accounts here..
		res.RV, err = sql.GetResourceVersion(ctx, "user", "updated")
	}

	return res, err
}

type ListServiceAccountTokenQuery struct {
	// UID is the service account uid.
	UID        string
	OrgID      int64
	Pagination common.Pagination
}

type ListServiceAccountTokenResult struct {
	Items    []ServiceAccountToken
	Continue int64
	RV       int64
}

type ServiceAccountToken struct {
	ID       int64
	Name     string
	Revoked  bool
	Expires  *int64
	LastUsed *time.Time
	Created  time.Time
	Updated  time.Time
}

var sqlQueryServiceAccountTokensTemplate = mustTemplate("service_account_tokens_query.sql")

func newListServiceAccountTokens(sql *legacysql.LegacyDatabaseHelper, q *ListServiceAccountTokenQuery) listServiceAccountTokensQuery {
	return listServiceAccountTokensQuery{
		SQLTemplate:  sqltemplate.New(sql.DialectForDriver()),
		UserTable:    sql.Table("user"),
		OrgUserTable: sql.Table("org_user"),
		TokenTable:   sql.Table("api_key"),
		Query:        q,
	}
}

type listServiceAccountTokensQuery struct {
	sqltemplate.SQLTemplate
	Query        *ListServiceAccountTokenQuery
	UserTable    string
	TokenTable   string
	OrgUserTable string
}

func (s *legacySQLStore) ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error) {
	// for continue
	query.Pagination.Limit += 1
	query.OrgID = ns.OrgID
	if ns.OrgID == 0 {
		return nil, fmt.Errorf("expected non zero orgID")
	}

	sql, err := s.sql(ctx)
	if err != nil {
		return nil, err
	}

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

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

	res := &ListServiceAccountTokenResult{}
	if err != nil {
		return nil, err
	}

	var lastID int64
	for rows.Next() {
		var t ServiceAccountToken
		err := rows.Scan(&t.ID, &t.Name, &t.Revoked, &t.LastUsed, &t.Expires, &t.Created, &t.Updated)
		if err != nil {
			return res, err
		}

		lastID = t.ID
		res.Items = append(res.Items, t)
		if len(res.Items) > int(query.Pagination.Limit)-1 {
			res.Items = res.Items[0 : len(res.Items)-1]
			res.Continue = lastID
			break
		}
	}

	return res, err
}
