package repository

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/spf13/viper"

	v1 "template-plus/api/v1"
	"template-plus/internal/model"
	"template-plus/pkg/sid"
)

type SysTokenRepository interface {
	InsertToken(ctx context.Context, token *model.SysToken) (sql.Result, error)
	DeleteToken(ctx context.Context, userID int64) (sql.Result, error)
	RevokeToken(ctx context.Context, id int64) (sql.Result, error)
	GetTokenByUserID(ctx context.Context, userID int64) (*model.SysToken, error)
	SelectTokenByPage(ctx context.Context, pageInfo *v1.TokenPageInfo) ([]model.SysToken, int64, error)
}

type sysTokenRepository struct {
	generic genericRepository[model.SysToken]
}

func NewSysTokenRepository(r *Repository, v *viper.Viper, s *sid.Sid) SysTokenRepository {
	return &sysTokenRepository{genericRepository[model.SysToken]{Repository: r, isDev: v.GetString("env") == "dev", s: s}}
}

const (
	insertTokenSQL = `INSERT INTO sys_tokens (id, user_id, access_token, refresh_token, expires_at, created_at, revoked, login_ip, device_fingerprint) VALUES (:id, :user_id, :access_token, :refresh_token, :expires_at, :created_at, :revoked, :login_ip, :device_fingerprint)`

	deleteTokenSQL = `DELETE FROM sys_tokens WHERE user_id = $1`

	revokeTokenSQL = `UPDATE sys_tokens SET revoked = TRUE WHERE id = :id`

	getTokenByUserIDSQL = `SELECT id, user_id, access_token, refresh_token, expires_at, created_at, revoked, login_ip, device_fingerprint FROM sys_tokens WHERE user_id = $1`

	countTokenSQL = `SELECT COUNT(*) FROM sys_tokens`

	baseTokenQuery = `SELECT st.id, st.user_id, su.username, st.access_token, st.refresh_token, st.expires_at, st.created_at, st.revoked, st.login_ip, st.device_fingerprint FROM sys_tokens st LEFT JOIN public.sys_user su ON su.id = st.user_id`
)

func (s *sysTokenRepository) InsertToken(ctx context.Context, token *model.SysToken) (sql.Result, error) {
	if token == nil {
		return nil, errors.New("token is nil")
	}
	return s.generic.Insert(ctx, insertTokenSQL, token)
}

func (s *sysTokenRepository) DeleteToken(ctx context.Context, userID int64) (sql.Result, error) {
	if userID <= 0 {
		return nil, errors.New("userID is invalid")
	}
	return s.generic.Delete(ctx, deleteTokenSQL, userID)
}

func (s *sysTokenRepository) RevokeToken(ctx context.Context, id int64) (sql.Result, error) {
	if id <= 0 {
		return nil, errors.New("id is invalid")
	}
	token := model.SysToken{ID: id}
	return s.generic.Update(ctx, revokeTokenSQL, &token)
}

func (s *sysTokenRepository) GetTokenByUserID(ctx context.Context, userID int64) (*model.SysToken, error) {
	if userID <= 0 {
		return nil, errors.New("userID is invalid")
	}
	return s.generic.Get(ctx, getTokenByUserIDSQL, userID)
}

func (s *sysTokenRepository) SelectTokenByPage(ctx context.Context, pageInfo *v1.TokenPageInfo) ([]model.SysToken, int64, error) {
	var (
		total  int64
		err    error
		query  strings.Builder
		params = make(map[string]any)
		order  = "DESC"
	)

	if pageInfo.Asc {
		order = "ASC"
	}

	// Build query
	query.WriteString(baseUserQuery)

	if pageInfo.Username != "" {
		query.WriteString(" WHERE u.username LIKE :username")
		params["username"] = pageInfo.Username + "%"
		total, err = s.generic.Count(ctx, countUserSQL+" WHERE username LIKE $1", pageInfo.Username+"%")
	} else {
		total, err = s.generic.Count(ctx, countUserSQL)
	}

	if err != nil {
		return nil, 0, err
	}

	query.WriteString(fmt.Sprintf(" ORDER BY %s %s LIMIT :size OFFSET :offset", pageInfo.Sort, order))
	params["size"] = pageInfo.Size
	params["offset"] = pageInfo.Offset

	tokens, err := s.generic.SelectNamed(ctx, query.String(), params)
	if err != nil {
		return nil, 0, err
	}
	return tokens, total, nil
}
format the code and add comments