package datatable

import (
	"github.com/mayswind/ezbookkeeping/pkg/core"
	"github.com/mayswind/ezbookkeeping/pkg/errs"
	"github.com/mayswind/ezbookkeeping/pkg/log"
	"github.com/mayswind/ezbookkeeping/pkg/models"
)

// ImportedTransactionDataTable defines the structure of imported transaction data table
type ImportedTransactionDataTable struct {
	innerDataTable    ImportedDataTable
	dataColumnMapping map[TransactionDataTableColumn]string
	dataColumnIndexes map[TransactionDataTableColumn]int
	rowParser         TransactionDataRowParser
	addedColumns      map[TransactionDataTableColumn]bool
}

// ImportedTransactionDataRow defines the structure of imported transaction data row
type ImportedTransactionDataRow struct {
	transactionDataTable *ImportedTransactionDataTable
	rowData              map[TransactionDataTableColumn]string
	rowDataValid         bool
}

// ImportedTransactionDataRowIterator defines the structure of imported transaction data row iterator
type ImportedTransactionDataRowIterator struct {
	transactionDataTable *ImportedTransactionDataTable
	innerIterator        ImportedDataRowIterator
}

// HasColumn returns whether the data table has specified column
func (t *ImportedTransactionDataTable) HasColumn(column TransactionDataTableColumn) bool {
	index, exists := t.dataColumnIndexes[column]

	if exists && index >= 0 {
		return exists
	}

	if t.addedColumns != nil {
		_, exists = t.addedColumns[column]

		if exists {
			return exists
		}
	}

	return false
}

// TransactionRowCount returns the total count of transaction data row
func (t *ImportedTransactionDataTable) TransactionRowCount() int {
	return t.innerDataTable.DataRowCount()
}

// TransactionRowIterator returns the iterator of transaction data row
func (t *ImportedTransactionDataTable) TransactionRowIterator() TransactionDataRowIterator {
	return &ImportedTransactionDataRowIterator{
		transactionDataTable: t,
		innerIterator:        t.innerDataTable.DataRowIterator(),
	}
}

// IsValid returns whether this row is valid data for importing
func (r *ImportedTransactionDataRow) IsValid() bool {
	return r.rowDataValid
}

// GetData returns the data in the specified column type
func (r *ImportedTransactionDataRow) GetData(column TransactionDataTableColumn) string {
	if !r.rowDataValid {
		return ""
	}

	_, exists := r.transactionDataTable.dataColumnIndexes[column]

	if exists {
		return r.rowData[column]
	}

	if r.transactionDataTable.addedColumns != nil {
		_, exists = r.transactionDataTable.addedColumns[column]

		if exists {
			return r.rowData[column]
		}
	}

	return ""
}

// HasNext returns whether the iterator does not reach the end
func (t *ImportedTransactionDataRowIterator) HasNext() bool {
	return t.innerIterator.HasNext()
}

// Next returns the next transaction data row
func (t *ImportedTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow TransactionDataRow, err error) {
	importedRow := t.innerIterator.Next()

	if importedRow == nil {
		return nil, nil
	}

	if importedRow.ColumnCount() == 1 && importedRow.GetData(0) == "" {
		return &ImportedTransactionDataRow{
			transactionDataTable: t.transactionDataTable,
			rowData:              nil,
			rowDataValid:         false,
		}, nil
	}

	if importedRow.ColumnCount() < len(t.transactionDataTable.dataColumnIndexes) {
		log.Errorf(ctx, "[imported_transaction_data_table.Next] cannot parse data row, because may missing some columns (column count %d in data row is less than header column count %d)", importedRow.ColumnCount(), len(t.transactionDataTable.dataColumnIndexes))
		return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
	}

	rowData := make(map[TransactionDataTableColumn]string, len(t.transactionDataTable.dataColumnIndexes))
	rowDataValid := true

	for column, columnIndex := range t.transactionDataTable.dataColumnIndexes {
		if columnIndex < 0 || columnIndex >= importedRow.ColumnCount() {
			continue
		}

		value := importedRow.GetData(columnIndex)
		rowData[column] = value
	}

	if t.transactionDataTable.rowParser != nil {
		rowData, rowDataValid, err = t.transactionDataTable.rowParser.Parse(rowData)

		if err != nil {
			log.Errorf(ctx, "[imported_transaction_data_table.Next] cannot parse data row, because %s", err.Error())
			return nil, err
		}
	}

	return &ImportedTransactionDataRow{
		transactionDataTable: t.transactionDataTable,
		rowData:              rowData,
		rowDataValid:         rowDataValid,
	}, nil
}

// CreateNewImportedTransactionDataTable returns transaction data table from imported data table
func CreateNewImportedTransactionDataTable(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string) *ImportedTransactionDataTable {
	return CreateNewImportedTransactionDataTableWithRowParser(dataTable, dataColumnMapping, nil)
}

// CreateNewImportedTransactionDataTableWithRowParser returns transaction data table from imported data table
func CreateNewImportedTransactionDataTableWithRowParser(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string, rowParser TransactionDataRowParser) *ImportedTransactionDataTable {
	headerLineItems := dataTable.HeaderColumnNames()
	headerItemMap := make(map[string]int, len(headerLineItems))

	for i := 0; i < len(headerLineItems); i++ {
		headerItemMap[headerLineItems[i]] = i
	}

	dataColumnIndexes := make(map[TransactionDataTableColumn]int, len(headerLineItems))

	for column, columnName := range dataColumnMapping {
		columnIndex, exists := headerItemMap[columnName]

		if exists {
			dataColumnIndexes[column] = columnIndex
		}
	}

	var addedColumns map[TransactionDataTableColumn]bool

	if rowParser != nil {
		addedColumnsByParser := rowParser.GetAddedColumns()
		addedColumns = make(map[TransactionDataTableColumn]bool, len(addedColumnsByParser))

		for i := 0; i < len(addedColumnsByParser); i++ {
			addedColumns[addedColumnsByParser[i]] = true
		}
	}

	return &ImportedTransactionDataTable{
		innerDataTable:    dataTable,
		dataColumnMapping: dataColumnMapping,
		dataColumnIndexes: dataColumnIndexes,
		rowParser:         rowParser,
		addedColumns:      addedColumns,
	}
}
