package benchmarks

import (
	"database/sql"
	"log"
	"testing"

	_ "github.com/lib/pq"
)

const (
	accountID  = "64f2cd7a7047f28fdabf6d9e"
	connStr    = "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"
	queryTotal = `
		WITH user_data AS (
			SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false
		)
		SELECT f.*, COUNT(*) OVER() as total_count
		FROM fields f JOIN user_data u ON f.AccountID = u.AmoID
		WHERE f.Deleted = false
		ORDER BY f.ID OFFSET ($2 - 1) * $3 LIMIT $3;
	`
	queryCount = `
		WITH user_data AS (
			SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false
		)
		SELECT COUNT(*)
		FROM fields f JOIN user_data u ON f.AccountID = u.AmoID
		WHERE f.Deleted = false;
	`
	queryData = `
		WITH user_data AS (
			SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false
		)
		SELECT f.*
		FROM fields f JOIN user_data u ON f.AccountID = u.AmoID
		WHERE f.Deleted = false
		ORDER BY f.ID OFFSET ($2 - 1) * $3 LIMIT $3;
	`
)

type GetFieldsWithPaginationRow struct {
	ID         int64        `db:"id" json:"id"`
	Amoid      int32        `db:"amoid" json:"amoid"`
	Code       string       `db:"code" json:"code"`
	Accountid  int32        `db:"accountid" json:"accountid"`
	Name       string       `db:"name" json:"name"`
	Entity     interface{}  `db:"entity" json:"entity"`
	Type       interface{}  `db:"type" json:"type"`
	Deleted    bool         `db:"deleted" json:"deleted"`
	Createdat  sql.NullTime `db:"createdat" json:"createdat"`
	TotalCount int64        `db:"total_count" json:"total_count"`
}

func initDB() *sql.DB {
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		log.Fatal(err)
	}
	return db
}

// Все получаем в одном запросе не аллоцируя при этом массив
func BenchmarkAllOne(b *testing.B) {
	db := initDB()
	defer db.Close()
	for i := 0; i < b.N; i++ {
		page := 1
		size := 25
		rows, err := db.Query(queryTotal, accountID, page, size)
		if err != nil {
			b.Fatal(err)
		}
		defer rows.Close()

		var results []GetFieldsWithPaginationRow
		for rows.Next() {
			var row GetFieldsWithPaginationRow
			if err := rows.Scan(
				&row.ID,
				&row.Amoid,
				&row.Code,
				&row.Accountid,
				&row.Name,
				&row.Entity,
				&row.Type,
				&row.Deleted,
				&row.Createdat,
				&row.TotalCount,
			); err != nil {
				b.Fatal(err)
			}
			results = append(results, row)
		}

		if err := rows.Err(); err != nil {
			b.Fatal(err)
		}
	}
}

// Все получаем в одном запросе аллоцируя при этом массив
func BenchmarkAllOnePreAllocation(b *testing.B) {
	db := initDB()
	defer db.Close()
	for i := 0; i < b.N; i++ {
		page := 1
		size := 25
		rows, err := db.Query(queryTotal, accountID, page, size)
		if err != nil {
			b.Fatal(err)
		}
		defer rows.Close()

		results := make([]GetFieldsWithPaginationRow, size)
		for rows.Next() {
			var row GetFieldsWithPaginationRow
			if err := rows.Scan(
				&row.ID,
				&row.Amoid,
				&row.Code,
				&row.Accountid,
				&row.Name,
				&row.Entity,
				&row.Type,
				&row.Deleted,
				&row.Createdat,
				&row.TotalCount,
			); err != nil {
				b.Fatal(err)
			}
			results = append(results, row)
		}

		if err := rows.Err(); err != nil {
			b.Fatal(err)
		}
	}
}

// Считается сначала количество потом получаются данные длину и емкость массиву не меняем
func BenchmarkCountThenGetData(b *testing.B) {
	db := initDB()
	defer db.Close()
	for i := 0; i < b.N; i++ {
		page := 1
		size := 25

		row := db.QueryRow(queryCount, accountID)
		var totalCount int
		if err := row.Scan(&totalCount); err != nil {
			b.Fatal(err)
		}
		var results []GetFieldsWithPaginationRow
		rows, err := db.Query(queryData, accountID, page, size)
		if err != nil {
			b.Fatal(err)
		}
		defer rows.Close()

		for rows.Next() {
			var row GetFieldsWithPaginationRow
			if err := rows.Scan(
				&row.ID,
				&row.Amoid,
				&row.Code,
				&row.Accountid,
				&row.Name,
				&row.Entity,
				&row.Type,
				&row.Deleted,
				&row.Createdat,
			); err != nil {
				b.Fatal(err)
			}
			results = append(results, row)
		}

		if err := rows.Err(); err != nil {
			b.Fatal(err)
		}
	}
}

// Параллельное вычисление данных и общего количество при этом длина слайса = size
func BenchmarkParallel(b *testing.B) {
	db := initDB()
	defer db.Close()

	for i := 0; i < b.N; i++ {
		page := 1
		size := 25
		results := make([]GetFieldsWithPaginationRow, size)
		channel := make(chan error, 2)

		go func() {
			row := db.QueryRow(queryCount, accountID)
			var totalCount int
			channel <- row.Scan(&totalCount)
		}()

		go func() {
			rows, err := db.Query(queryData, accountID, page, size)
			if err != nil {
				channel <- err
				return
			}
			defer rows.Close()

			index := 0
			for rows.Next() {
				if err := rows.Scan(
					&results[index].ID,
					&results[index].Amoid,
					&results[index].Code,
					&results[index].Accountid,
					&results[index].Name,
					&results[index].Entity,
					&results[index].Type,
					&results[index].Deleted,
					&results[index].Createdat,
				); err != nil {
					channel <- err
					return
				}
				index++
			}
			channel <- rows.Err()
		}()

		for i := 0; i < 2; i++ {
			if err := <-channel; err != nil {
				b.Fatal(err)
			}
		}
	}
}

// Считается сначала количество потом получаются данные создаем слайс через маке указывая ему длину начальную кап = лен
func BenchmarkWithPreAllocation(b *testing.B) {
	db := initDB()
	defer db.Close()

	for i := 0; i < b.N; i++ {
		page := 1
		size := 25
		results := make([]GetFieldsWithPaginationRow, size)

		row := db.QueryRow(queryCount, accountID)
		var totalCount int
		if err := row.Scan(&totalCount); err != nil {
			b.Fatal(err)
		}
		rows, err := db.Query(queryData, accountID, page, size)
		if err != nil {
			b.Fatal(err)
		}
		defer rows.Close()

		index := 0
		for rows.Next() {
			if err := rows.Scan(
				&results[index].ID,
				&results[index].Amoid,
				&results[index].Code,
				&results[index].Accountid,
				&results[index].Name,
				&results[index].Entity,
				&results[index].Type,
				&results[index].Deleted,
				&results[index].Createdat,
			); err != nil {
				b.Fatal(err)
			}
			index++
		}

		if err := rows.Err(); err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkWithPreAllocationAndMonitoringTotalCount(b *testing.B) {
	db := initDB()
	defer db.Close()

	for i := 0; i < b.N; i++ {
		page := 1
		size := 50

		row := db.QueryRow(queryCount, accountID)
		var totalCount int
		if err := row.Scan(&totalCount); err != nil {
			b.Fatal(err)
		}
		if totalCount < size {
			size = totalCount
		}
		results := make([]GetFieldsWithPaginationRow, size)
		rows, err := db.Query(queryData, accountID, page, size)
		if err != nil {
			b.Fatal(err)
		}
		defer rows.Close()

		index := 0
		for rows.Next() {
			if err := rows.Scan(
				&results[index].ID,
				&results[index].Amoid,
				&results[index].Code,
				&results[index].Accountid,
				&results[index].Name,
				&results[index].Entity,
				&results[index].Type,
				&results[index].Deleted,
				&results[index].Createdat,
			); err != nil {
				b.Fatal(err)
			}
			index++
		}

		if err := rows.Err(); err != nil {
			b.Fatal(err)
		}
	}
}
