package quotation_service_v2

import (
	"strings"

	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
	accountingv1 "github.com/tech/sendico/pkg/proto/common/accounting/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
)

func feeLinesFromProto(lines []*feesv1.DerivedPostingLine) []*paymenttypes.FeeLine {
	if len(lines) == 0 {
		return nil
	}
	result := make([]*paymenttypes.FeeLine, 0, len(lines))
	for _, line := range lines {
		if line == nil {
			continue
		}
		result = append(result, &paymenttypes.FeeLine{
			LedgerAccountRef: strings.TrimSpace(line.GetLedgerAccountRef()),
			Money:            modelMoneyFromProto(line.GetMoney()),
			LineType:         postingLineTypeFromProto(line.GetLineType()),
			Side:             entrySideFromProto(line.GetSide()),
			Meta:             cloneStringMap(line.GetMeta()),
		})
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func feeLinesToProto(lines []*paymenttypes.FeeLine) []*feesv1.DerivedPostingLine {
	if len(lines) == 0 {
		return nil
	}
	result := make([]*feesv1.DerivedPostingLine, 0, len(lines))
	for _, line := range lines {
		if line == nil {
			continue
		}
		result = append(result, &feesv1.DerivedPostingLine{
			LedgerAccountRef: strings.TrimSpace(line.GetLedgerAccountRef()),
			Money:            protoMoneyFromModel(line.GetMoney()),
			LineType:         postingLineTypeToProto(line.GetLineType()),
			Side:             entrySideToProto(line.GetSide()),
			Meta:             cloneStringMap(line.Meta),
		})
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func feeRulesFromProto(rules []*feesv1.AppliedRule) []*paymenttypes.AppliedRule {
	if len(rules) == 0 {
		return nil
	}
	result := make([]*paymenttypes.AppliedRule, 0, len(rules))
	for _, rule := range rules {
		if rule == nil {
			continue
		}
		result = append(result, &paymenttypes.AppliedRule{
			RuleID:      strings.TrimSpace(rule.GetRuleId()),
			RuleVersion: strings.TrimSpace(rule.GetRuleVersion()),
			Formula:     strings.TrimSpace(rule.GetFormula()),
			Rounding:    roundingModeFromProto(rule.GetRounding()),
			TaxCode:     strings.TrimSpace(rule.GetTaxCode()),
			TaxRate:     strings.TrimSpace(rule.GetTaxRate()),
			Parameters:  cloneStringMap(rule.GetParameters()),
		})
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func feeRulesToProto(rules []*paymenttypes.AppliedRule) []*feesv1.AppliedRule {
	if len(rules) == 0 {
		return nil
	}
	result := make([]*feesv1.AppliedRule, 0, len(rules))
	for _, rule := range rules {
		if rule == nil {
			continue
		}
		result = append(result, &feesv1.AppliedRule{
			RuleId:      strings.TrimSpace(rule.RuleID),
			RuleVersion: strings.TrimSpace(rule.RuleVersion),
			Formula:     strings.TrimSpace(rule.Formula),
			Rounding:    roundingModeToProto(rule.Rounding),
			TaxCode:     strings.TrimSpace(rule.TaxCode),
			TaxRate:     strings.TrimSpace(rule.TaxRate),
			Parameters:  cloneStringMap(rule.Parameters),
		})
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func entrySideFromProto(side accountingv1.EntrySide) paymenttypes.EntrySide {
	switch side {
	case accountingv1.EntrySide_ENTRY_SIDE_DEBIT:
		return paymenttypes.EntrySideDebit
	case accountingv1.EntrySide_ENTRY_SIDE_CREDIT:
		return paymenttypes.EntrySideCredit
	default:
		return paymenttypes.EntrySideUnspecified
	}
}

func entrySideToProto(side paymenttypes.EntrySide) accountingv1.EntrySide {
	switch side {
	case paymenttypes.EntrySideDebit:
		return accountingv1.EntrySide_ENTRY_SIDE_DEBIT
	case paymenttypes.EntrySideCredit:
		return accountingv1.EntrySide_ENTRY_SIDE_CREDIT
	default:
		return accountingv1.EntrySide_ENTRY_SIDE_UNSPECIFIED
	}
}

func postingLineTypeFromProto(lineType accountingv1.PostingLineType) paymenttypes.PostingLineType {
	switch lineType {
	case accountingv1.PostingLineType_POSTING_LINE_FEE:
		return paymenttypes.PostingLineTypeFee
	case accountingv1.PostingLineType_POSTING_LINE_TAX:
		return paymenttypes.PostingLineTypeTax
	case accountingv1.PostingLineType_POSTING_LINE_SPREAD:
		return paymenttypes.PostingLineTypeSpread
	case accountingv1.PostingLineType_POSTING_LINE_REVERSAL:
		return paymenttypes.PostingLineTypeReversal
	default:
		return paymenttypes.PostingLineTypeUnspecified
	}
}

func postingLineTypeToProto(lineType paymenttypes.PostingLineType) accountingv1.PostingLineType {
	switch lineType {
	case paymenttypes.PostingLineTypeFee:
		return accountingv1.PostingLineType_POSTING_LINE_FEE
	case paymenttypes.PostingLineTypeTax:
		return accountingv1.PostingLineType_POSTING_LINE_TAX
	case paymenttypes.PostingLineTypeSpread:
		return accountingv1.PostingLineType_POSTING_LINE_SPREAD
	case paymenttypes.PostingLineTypeReversal:
		return accountingv1.PostingLineType_POSTING_LINE_REVERSAL
	default:
		return accountingv1.PostingLineType_POSTING_LINE_TYPE_UNSPECIFIED
	}
}

func roundingModeFromProto(mode moneyv1.RoundingMode) paymenttypes.RoundingMode {
	switch mode {
	case moneyv1.RoundingMode_ROUND_HALF_EVEN:
		return paymenttypes.RoundingModeHalfEven
	case moneyv1.RoundingMode_ROUND_HALF_UP:
		return paymenttypes.RoundingModeHalfUp
	case moneyv1.RoundingMode_ROUND_DOWN:
		return paymenttypes.RoundingModeDown
	default:
		return paymenttypes.RoundingModeUnspecified
	}
}

func roundingModeToProto(mode paymenttypes.RoundingMode) moneyv1.RoundingMode {
	switch mode {
	case paymenttypes.RoundingModeHalfEven:
		return moneyv1.RoundingMode_ROUND_HALF_EVEN
	case paymenttypes.RoundingModeHalfUp:
		return moneyv1.RoundingMode_ROUND_HALF_UP
	case paymenttypes.RoundingModeDown:
		return moneyv1.RoundingMode_ROUND_DOWN
	default:
		return moneyv1.RoundingMode_ROUNDING_MODE_UNSPECIFIED
	}
}

func cloneStringMap(input map[string]string) map[string]string {
	if len(input) == 0 {
		return nil
	}
	result := make(map[string]string, len(input))
	for k, v := range input {
		result[k] = v
	}
	return result
}
