package sql

// |@@| C

import (
	"context"
	"database/sql"
	erro "gardening/src/lib/error"
	"sync"
)

type MockCall struct {
	Query string
	Args  []any
}

type MockExecResult struct {
	LastInsertId int64
	RowsAffected int64
	Err          error
}

type MockQueryResult struct {
	Rows *sql.Rows
	Err  error
}

type MockQueryRowResult struct {
	Row *sql.Row
}

type MockSqlIpl struct {
	Data struct {
		mu sync.RWMutex

		// Store registered responses
		execResponses     []MockExecResult
		queryResponses    []MockQueryResult
		queryRowResponses []MockQueryRowResult

		// Store actual calls made
		execCalls     []MockCall
		queryCalls    []MockCall
		queryRowCalls []MockCall

		// Call counters
		execCallCount     int
		queryCallCount    int
		queryRowCallCount int
	}
}

func (this *MockSqlIpl) RegisterExecResponse(lastInsertId int64, rowsAffected int64, err error) {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()
	this.Data.execResponses = append(this.Data.execResponses, MockExecResult{
		LastInsertId: lastInsertId,
		RowsAffected: rowsAffected,
		Err:          err,
	})
}

func (this *MockSqlIpl) RegisterQueryResponse(rows *sql.Rows, err error) {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()
	this.Data.queryResponses = append(this.Data.queryResponses, MockQueryResult{
		Rows: rows,
		Err:  err,
	})
}

func (this *MockSqlIpl) RegisterQueryRowResponse(row *sql.Row) {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()
	this.Data.queryRowResponses = append(this.Data.queryRowResponses, MockQueryRowResult{
		Row: row,
	})
}

func (this *MockSqlIpl) GetExecCalls() []MockCall {
	this.Data.mu.RLock()
	defer this.Data.mu.RUnlock()
	calls := make([]MockCall, len(this.Data.execCalls))
	copy(calls, this.Data.execCalls)
	return calls
}

func (this *MockSqlIpl) GetQueryCalls() []MockCall {
	this.Data.mu.RLock()
	defer this.Data.mu.RUnlock()
	calls := make([]MockCall, len(this.Data.queryCalls))
	copy(calls, this.Data.queryCalls)
	return calls
}

func (this *MockSqlIpl) GetQueryRowCalls() []MockCall {
	this.Data.mu.RLock()
	defer this.Data.mu.RUnlock()
	calls := make([]MockCall, len(this.Data.queryRowCalls))
	copy(calls, this.Data.queryRowCalls)
	return calls
}

func (this *MockSqlIpl) Reset() {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()
	this.Data.execResponses = nil
	this.Data.queryResponses = nil
	this.Data.queryRowResponses = nil
	this.Data.execCalls = nil
	this.Data.queryCalls = nil
	this.Data.queryRowCalls = nil
	this.Data.execCallCount = 0
	this.Data.queryCallCount = 0
	this.Data.queryRowCallCount = 0
}

type mockResult struct {
	lastInsertId int64
	rowsAffected int64
}

func (r mockResult) LastInsertId() (int64, error) {
	return r.lastInsertId, nil
}

func (r mockResult) RowsAffected() (int64, error) {
	return r.rowsAffected, nil
}

func (this *MockSqlIpl) Kind() string {
	return "mock"
}

func (this *MockSqlIpl) Driver() string {
	return ""
}

func (this *MockSqlIpl) DataSourceName(ctx context.Context, rootDir string) string {
	return ""
}

func (this *MockSqlIpl) SchemaPath(ctx context.Context, rootDir string) string {
	return ""
}

func (this *MockSqlIpl) IsInMemory(ctx context.Context) bool {
	return true
}

func (this *MockSqlIpl) SetMaxOpenConns(db *sql.DB, n int) {

}

func (this *MockSqlIpl) Exec(db *sql.DB, query string, args ...any) (sql.Result, error) {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()

	this.Data.execCalls = append(this.Data.execCalls, MockCall{
		Query: query,
		Args:  args,
	})

	if this.Data.execCallCount >= len(this.Data.execResponses) {
		panic(erro.N("no more exec responses registered"))
	}

	response := this.Data.execResponses[this.Data.execCallCount]
	this.Data.execCallCount++

	if response.Err != nil {
		return nil, response.Err
	}

	return mockResult{
		lastInsertId: response.LastInsertId,
		rowsAffected: response.RowsAffected,
	}, nil
}

func (this *MockSqlIpl) BeginTx(ctx context.Context, db *sql.DB, opts *sql.TxOptions) (*sql.Tx, error) {
	panic("implement me")
}

func (this *MockSqlIpl) Open(dataSourceName string) (*sql.DB, error) {
	return &sql.DB{}, nil
}

func (this *MockSqlIpl) Query(db *sql.DB, query string, args ...any) (*sql.Rows, error) {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()

	this.Data.queryCalls = append(this.Data.queryCalls, MockCall{
		Query: query,
		Args:  args,
	})

	if this.Data.queryCallCount >= len(this.Data.queryResponses) {
		panic(erro.N("no more query responses registered"))
	}

	response := this.Data.queryResponses[this.Data.queryCallCount]
	this.Data.queryCallCount++

	return response.Rows, response.Err
}

func (this *MockSqlIpl) QueryRow(db *sql.DB, query string, args ...any) *sql.Row {
	this.Data.mu.Lock()
	defer this.Data.mu.Unlock()

	this.Data.queryRowCalls = append(this.Data.queryRowCalls, MockCall{
		Query: query,
		Args:  args,
	})

	if this.Data.queryRowCallCount >= len(this.Data.queryRowResponses) {
		panic(erro.N("no more query row responses registered"))
	}

	response := this.Data.queryRowResponses[this.Data.queryRowCallCount]
	this.Data.queryRowCallCount++

	return response.Row
}

func (this *MockSqlIpl) Prepare(db *sql.DB, query string) (*sql.Stmt, error) {
	panic("implement me")
}

func (this *MockSqlIpl) Create(ctx context.Context, dataSourceName string) {
	panic("implement me")
}

func (this *MockSqlIpl) Destroy(ctx context.Context, dataSourceName string) {
	panic("implement me")
}

func (this *MockSqlIpl) Placeholder(n int) string {
	return "?"
}
