package sqlite

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

	"git.unistack.org/unistack-org/pkgdash/internal/models"
	"git.unistack.org/unistack-org/pkgdash/internal/storage"
	pb "git.unistack.org/unistack-org/pkgdash/proto"
	"github.com/jmoiron/sqlx"
	"go.unistack.org/micro/v4/logger"
)

func init() {
	storage.RegisterStorage("sqlite", NewStorage())
}

var _ storage.Storage = (*Sqlite)(nil)

type Sqlite struct {
	db *sqlx.DB
}

func NewStorage() func(*sqlx.DB) interface{} {
	return func(db *sqlx.DB) interface{} {
		return &Sqlite{db: db}
	}
}

func (s *Sqlite) PackageModulesCreate(ctx context.Context, pkg *models.Package, modules []*models.Module) error {
	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return err
	}

	for _, mod := range modules {
		err = tx.GetContext(ctx, mod, queryModulesCreate, mod.Name, mod.Version)
		if err != nil {
			_ = tx.Rollback()
			return err
		}
		_, err = tx.ExecContext(ctx, queryPackagesModulesCreate, pkg.ID, mod.ID)
		if err != nil {
			_ = tx.Rollback()
			return err
		}

	}

	_, err = tx.ExecContext(ctx, queryPackageModulesCount, pkg.ID, len(modules))
	if err != nil {
		_ = tx.Rollback()
		return err
	}

	if err = tx.Commit(); err != nil {
		_ = tx.Rollback()
		return err
	}

	return nil
}

func (s *Sqlite) PackageDelete(ctx context.Context, req *pb.PackageDeleteReq) error {
	return fmt.Errorf("need implement")
}

func (s *Sqlite) PackageUpdate(ctx context.Context, req *pb.PackageUpdateReq) (*models.Package, error) {
	return nil, fmt.Errorf("need implement")
}

func (s *Sqlite) PackageLookup(ctx context.Context, req *pb.PackageLookupReq) (*models.Package, error) {
	pkg := &models.Package{}

	err := s.db.GetContext(ctx, pkg, queryPackagesLookup, req.Id)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return pkg, err
}

func (s *Sqlite) PackageList(ctx context.Context, req *pb.PackageListReq) ([]*models.Package, error) {
	var packages []*models.Package

	err := s.db.SelectContext(ctx, &packages, queryPackagesList)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return packages, nil
}

func (s *Sqlite) PackageModules(ctx context.Context, req *pb.PackageModulesReq) ([]*models.Module, error) {
	var modules []*models.Module

	err := s.db.SelectContext(ctx, &modules, queryPackagesModules, req.Package)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return modules, nil
}

func (s *Sqlite) CommentDelete(ctx context.Context, req *pb.CommentDeleteReq) error {
	return nil
}

func (s *Sqlite) CommentCreate(ctx context.Context, req *pb.CommentCreateReq) (*models.Comment, error) {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			if rollbackErr := tx.Rollback(); rollbackErr != nil {
				logger.Error(ctx, "AddComment: unable to rollback: %v", rollbackErr)
			}
		} else {
			err = tx.Commit()
		}
	}()

	if _, err = tx.ExecContext(ctx, queryCommentsCreate, req.Comment, req.PackageId); err != nil {
		return nil, err
	}

	return nil, nil
}

func (s *Sqlite) PackagesProcess(ctx context.Context, td time.Duration) ([]*models.Package, error) {
	var packages []*models.Package
	err := s.db.SelectContext(ctx, &packages, queryPackagesProcess, td.Seconds())
	if err != nil {
		return nil, err
	}

	return packages, nil
}

func (s *Sqlite) PackagesUpdateLastCheck(ctx context.Context, packages []*models.Package) error {
	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return err
	}
	for _, pkg := range packages {
		if _, err = tx.ExecContext(ctx, queryPackagesUpdateLastCheck, pkg.ID); err != nil {
			tx.Rollback()
			return err
		}
	}

	if err = tx.Commit(); err != nil {
		tx.Rollback()
		return err
	}

	return nil
}

func (s *Sqlite) ModulesProcess(ctx context.Context, td time.Duration) ([]*models.Module, error) {
	var modules []*models.Module
	err := s.db.SelectContext(ctx, &modules, queryModulesProcess, td.Seconds())
	if err != nil {
		return nil, err
	}

	return modules, nil
}

func (s *Sqlite) PackageCreate(ctx context.Context, req *pb.PackageCreateReq) (*models.Package, error) {
	pkg := &models.Package{}
	err := s.db.GetContext(ctx, pkg, queryPackagesCreate, req.Name, req.Url)
	if err != nil {
		return nil, err
	}

	return pkg, nil
}

func (s *Sqlite) ModuleCreate(ctx context.Context, modules []*models.Module) error {
	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return err
	}

	for _, mod := range modules {
		err = tx.GetContext(ctx, mod, queryModulesCreate, mod.Name, mod.Version)
		if err != nil {
			_ = tx.Rollback()
			return err
		}
	}

	if err = tx.Commit(); err != nil {
		_ = tx.Rollback()
		return err
	}

	return nil
}

func (s *Sqlite) ModuleList(ctx context.Context, req *pb.ModuleListReq) ([]*models.Module, error) {
	var modules []*models.Module

	err := s.db.SelectContext(ctx, &modules, queryModulesList)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return modules, nil
}

func (s *Sqlite) CommentList(ctx context.Context, req *pb.CommentListReq) ([]*models.Comment, error) {
	var comments []*models.Comment

	err := s.db.SelectContext(ctx, &comments, queryCommentsList, req.Package)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return comments, nil
}

func (s *Sqlite) HandlerList(ctx context.Context, req *pb.HandlerListReq) ([]*models.Handler, error) {
	var handlers []*models.Handler

	err := s.db.SelectContext(ctx, &handlers, queryHandlersList, req.Package)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return handlers, nil
}
