//go:build ignore

package postgres

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

	"git.unistack.org/unistack-org/pkgdash/internal/config"
	"git.unistack.org/unistack-org/pkgdash/internal/models"
	pb "git.unistack.org/unistack-org/pkgdash/proto"
	"github.com/golang-migrate/migrate/v4"
	mpgx "github.com/golang-migrate/migrate/v4/database/pgx"
	"github.com/golang-migrate/migrate/v4/source/iofs"
	"github.com/lib/pq"
	"go.unistack.org/micro/v4/logger"
)

const (
	pathMigration = `migrations/postgres`
)

type Postgres struct {
	db *sql.DB
	fs embed.FS
}

func NewStorage() func(*sql.DB, embed.FS) interface{} {
	return func(db *sql.DB, fs embed.FS) interface{} {
		return &Postgres{db: db, fs: fs}
	}
}

func (s *Postgres) MigrateUp() error {
	driver, err := mpgx.WithInstance(s.db, &mpgx.Config{
		MigrationsTable: mpgx.DefaultMigrationsTable,
		DatabaseName:    config.ServiceName,
	})
	if err != nil {
		return err
	}
	source, err := iofs.New(s.fs, pathMigration)
	if err != nil {
		return err
	}

	// TODO: pass own logger
	m, err := migrate.NewWithInstance("fs", source, config.ServiceName, driver)
	if err != nil {
		return err
	}

	if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
		return err
	}

	return nil
}

func (s *Postgres) MigrateDown() error {
	driver, err := mpgx.WithInstance(s.db, &mpgx.Config{
		MigrationsTable: mpgx.DefaultMigrationsTable,
		DatabaseName:    config.ServiceName,
	})
	if err != nil {
		return err
	}
	source, err := iofs.New(s.fs, pathMigration)
	if err != nil {
		return err
	}

	// TODO: pass own logger
	m, err := migrate.NewWithInstance("fs", source, config.ServiceName, driver)
	if err != nil {
		return err
	}

	if err = m.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
		return err
	}

	return nil
}

func (s *Postgres) PackagesUpdate(ctx context.Context, req *pb.PackagesUpdateReq) error {
	panic("need implement")
}

func (s *Postgres) PackagesList(ctx context.Context, req *pb.PackagesListReq) (models.ListPackage, error) {
	rows, err := s.db.QueryContext(ctx, queryListPackage)
	if err != nil {
		return nil, err
	}

	defer func() {
		if err = rows.Close(); err != nil {
			return
		}
		err = rows.Err()
	}()

	result := make([]*models.Package, 0)
	for rows.Next() {
		tmp := &models.Package{}
		if err = rows.Scan(
			&tmp.ID,
			&tmp.Name,
			&tmp.URL,
			pq.Array(&tmp.Comments),
		); err != nil {
			return nil, err
		}
	}

	return result, err
}

func (s *Postgres) CommentsCreate(ctx context.Context, req *pb.CommentsCreateReq) error {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return 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()
		}
	}()

	res, err := tx.ExecContext(ctx, queryAddComment, req.Text, req.PackageId)
	if err != nil {
		return err
	}

	if aff, affErr := res.RowsAffected(); err != nil {
		err = affErr
	} else if aff == 0 {
		err = errors.New("rows affected is 0")
	}

	return err
}

func (s *Postgres) PackagesCreate(ctx context.Context, req *pb.PackagesCreateReq) error {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}

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

	res, err := tx.ExecContext(ctx, queryAddPackage, req.Name, req.Url, pq.Array(req.Modules))
	if err != nil {
		return err
	}

	if aff, affErr := res.RowsAffected(); err != nil {
		err = affErr
	} else if aff == 0 {
		err = errors.New("rows affected is 0")
	}

	return err
}

func (s *Postgres) InsertButchModules(ctx context.Context, req []models.Module) ([]uint64, 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, "AddPackage: unable to rollback: %v", rollbackErr)
			}
		} else {
			err = tx.Commit()
		}
	}()

	query := generateQuery(req)

	rows, err := tx.QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err = rows.Close(); err != nil {
			return
		}
		err = rows.Err()
	}()

	result := make([]uint64, 0)
	for rows.Next() {
		tmp := uint64(0)
		if err = rows.Scan(&tmp); err != nil {
			return nil, err
		}

		result = append(result, tmp)
	}

	return result, err
}

func generateQuery(rsp []models.Module) string {
	const pattern = `%c('%s', '%s', '%s')`
	build := strings.Builder{}
	comma := ' '
	for i := range rsp {
		str := fmt.Sprintf(pattern, comma, rsp[i].Name, rsp[i].Version, rsp[i].LastVersion)
		build.WriteString(str)
		comma = ','
	}

	return fmt.Sprintf(queryInsMsgGetIDs, build.String())
}
