package storage

import (
	"context"
	"errors"
	"fmt"
	"github.com/jackc/pgx/v5"
	"log"
	"madsky.ru/go-tracker/internal/database"
	"madsky.ru/go-tracker/internal/model/project"
	"strings"
)

type ProjectRepository interface {
	Find(ctx context.Context, userId uint32, isAdmin bool, filter *project.FilterDTO) ([]*project.Project, error)
	FindOne(ctx context.Context, projectId uint64, userId uint32, isAdmin bool) (*project.Project, error)
	Create(ctx context.Context, dto *project.CreateProjectDTO) (*project.Project, error)
	Update(ctx context.Context, id uint64, issue *project.UpdateProjectDTO) (*project.Project, error)
	Remove(ctx context.Context, id uint64) (uint64, error)
}
type ProjectStore struct {
	client database.Client
}

func (ps *ProjectStore) Find(ctx context.Context, userId uint32, isAdmin bool, filter *project.FilterDTO) ([]*project.Project, error) {
	query := `select p.id, p.name, p.description, p.key from projects p `

	var args []interface{}

	if isAdmin == false {
		query += `left join user_to_project up on up.project_id = p.id where up.user_id = $1`
		args = append(args, userId)
	}

	rows, err := ps.client.Query(ctx, query, args...)
	if err != nil {
		log.Println("ProjectStore rows", err)
		return nil, err
	}
	defer rows.Close()

	// another variant
	//projects, err := pgx.CollectRows(rows, pgx.RowToStructByName[project.Project])

	projects := make([]*project.Project, 0)

	for rows.Next() {
		var p project.Project

		err = rows.Scan(&p.ID, &p.Name, &p.Description, &p.Key)
		if err != nil {
			log.Println("scan", err)
			return nil, err
		}
		projects = append(projects, &p)
	}

	if err = rows.Err(); err != nil {
		log.Println("rows err", err)
		return nil, err
	}

	return projects, nil
}

func (ps *ProjectStore) FindOne(ctx context.Context, projectId uint64, userId uint32, isAdmin bool) (*project.Project, error) {
	args := []interface{}{projectId}

	query := `select p.id, p.name, p.description, p.key from projects p
			  left join user_to_project up on up.project_id = p.id
			  where p.id = $1 `

	if isAdmin == false {
		query += "and up.user_id = $2"
		args = append(args, userId)
	}

	var p project.Project

	rows, _ := ps.client.Query(ctx, query, args...)
	defer rows.Close()

	p, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[project.Project])
	if err != nil {
		fmt.Println("CollectOneRow FindById project", err)
		return nil, err
	}

	return &p, nil
}

func (ps *ProjectStore) Create(ctx context.Context, dto *project.CreateProjectDTO) (*project.Project, error) {
	//q := "insert into projects (name, description, key) values ($1, $2, $3) returning id, name, description, key"
	query := `insert into projects (name, description, key) 
			  values (@projName, @projDescription, @projKey) 
			  returning id, name, description, key`

	key := dto.Key

	if dto.Key == "" {
		key = trimString(dto.Name)
	}

	args := pgx.NamedArgs{
		"projName":        dto.Name,
		"projDescription": dto.Description,
		"projKey":         key,
	}

	//rows, _ := ps.client.Query(ctx, q, dto.Name, dto.Description, key)
	rows, _ := ps.client.Query(ctx, query, args)
	defer rows.Close()

	p, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[project.Project])
	if err != nil {
		fmt.Println("CollectOneRow Create Project", err)
		//return nil, fmt.Errorf("unable to insert row: %w", err)
		return nil, fmt.Errorf("KEY must be 4 characters long")
	}

	return &p, nil
}

func (ps *ProjectStore) Update(ctx context.Context, id uint64, dto *project.UpdateProjectDTO) (*project.Project, error) {
	q := `update projects 
          set name = $1, description = $2 
          where id = $3 
          returning id, name, description, key`

	var p project.Project

	if err := ps.client.QueryRow(ctx, q, dto.Name, dto.Description, id).Scan(&p.ID, &p.Name, &p.Description, &p.Key); err != nil {
		fmt.Println(fmt.Sprintf("error %v", err))
		return nil, err
	}

	return &p, nil
}

func (ps *ProjectStore) Remove(ctx context.Context, id uint64) (uint64, error) {
	q := `delete from projects where id=$1`

	tag, err := ps.client.Exec(ctx, q, id)
	if err != nil {
		log.Println("exec error", err)
		return 0, err
	}

	rowsAffected := tag.RowsAffected()

	if rowsAffected == 0 {
		return 0, errors.New("project not found")
	}

	return uint64(rowsAffected), nil
}

func trimString(str string) string {
	runeStr := []rune(str)
	var res string

	if len(runeStr) > 4 {
		res = string(runeStr[:3])
	} else {
		res = string(runeStr)
	}

	return strings.ToUpper(res)
}
