package service

import (
	"encoding/json"
	"fmt"
	"github.com/beancount-gs/script"
	"github.com/gin-gonic/gin"
	"regexp"
	"sort"
	"strings"
	"time"
)

func QueryValidAccount(c *gin.Context) {
	ledgerConfig := script.GetLedgerConfigFromContext(c)
	allAccounts := script.GetLedgerAccounts(ledgerConfig.Id)
	currencyMap := script.GetLedgerCurrencyMap(ledgerConfig.Id)
	result := make([]script.Account, 0)
	for _, account := range allAccounts {
		if account.EndDate == "" {
			// 多个货币处理
			multiCurrency := strings.Split(account.Currency, ",")
			account.Currency = multiCurrency[0]
			account.Currencies = multiCurrencies(*ledgerConfig, multiCurrency, currencyMap)
			result = append(result, account)
		}
	}
	OK(c, result)
}

type accountPosition struct {
	Account        string `json:"account"`
	MarketPosition string `json:"market_position"`
	Position       string `json:"position"`
}

func QueryAllAccount(c *gin.Context) {
	ledgerConfig := script.GetLedgerConfigFromContext(c)

	bql := fmt.Sprintf("select '\\', account, '\\', sum(convert(value(position), '%s')) as market_position, '\\', sum(convert(value(position), currency)) as position, '\\'", ledgerConfig.OperatingCurrency)
	accountPositions := make([]accountPosition, 0)
	err := script.BQLQueryListByCustomSelect(ledgerConfig, bql, nil, &accountPositions)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	// 将查询结果放入 map 中方便查询账户金额
	accountPositionMap := make(map[string]accountPosition)
	for _, ap := range accountPositions {
		accountPositionMap[ap.Account] = ap
	}

	currencyMap := script.GetLedgerCurrencyMap(ledgerConfig.Id)
	accounts := script.GetLedgerAccounts(ledgerConfig.Id)
	result := make([]script.Account, 0, len(accounts))
	for i := 0; i < len(accounts); i++ {
		account := accounts[i]
		// 过滤已结束的账户
		if account.EndDate != "" {
			continue
		}
		// 多个货币处理
		multiCurrency := strings.Split(account.Currency, ",")
		// 账户主货币
		account.Currency = multiCurrency[0]
		account.Currencies = multiCurrencies(*ledgerConfig, multiCurrency, currencyMap)

		key := account.Acc
		typ := script.GetAccountType(ledgerConfig.Id, key)
		account.Type = &typ
		marketPosition := strings.Trim(accountPositionMap[key].MarketPosition, " ")
		if marketPosition != "" {
			fields := strings.Fields(marketPosition)
			account.MarketNumber = fields[0]
			account.MarketCurrency = fields[1]
			account.MarketCurrencySymbol = script.GetCommoditySymbol(ledgerConfig.Id, fields[1])
		}
		position := strings.Trim(accountPositionMap[key].Position, " ")
		if position != "" {
			account.Positions = parseAccountPositions(ledgerConfig.Id, position)
		}
		result = append(result, account)
	}
	OK(c, result)
}

func multiCurrencies(ledgerConfig script.Config, multiCurrencyStr []string, currencyMap map[string]script.LedgerCurrency) []script.AccountCurrency {
	currencies := make([]script.AccountCurrency, 0)
	for i := 0; i < len(multiCurrencyStr); i++ {
		accCurrency := script.AccountCurrency{
			Currency:       multiCurrencyStr[i],
			CurrencySymbol: script.GetCommoditySymbol(ledgerConfig.Id, multiCurrencyStr[i]),
		}
		// 从 map 中获取对应货币的实时汇率和符号
		currency, ok := currencyMap[multiCurrencyStr[i]]
		if ok {
			accCurrency.CurrencySymbol = currency.Symbol
			accCurrency.Price = currency.Price
			accCurrency.PriceDate = currency.PriceDate
			accCurrency.IsAnotherCurrency = multiCurrencyStr[i] != ledgerConfig.OperatingCurrency
		}
		currencies = append(currencies, accCurrency)
	}
	return currencies
}

func parseAccountPositions(ledgerId string, input string) []script.AccountPosition {
	// 使用正则表达式提取数字、货币代码和金额
	re := regexp.MustCompile(`(-?\d+\.\d+) (\w+)`)
	matches := re.FindAllStringSubmatch(input, -1)

	var positions []script.AccountPosition

	// 遍历匹配项并创建 AccountPosition
	for _, match := range matches {
		number := match[1]
		currency := match[2]

		// 获取货币符号
		symbol := script.GetCommoditySymbol(ledgerId, currency)

		// 创建 AccountPosition
		position := script.AccountPosition{
			Number:         number,
			Currency:       currency,
			CurrencySymbol: symbol,
		}

		// 添加到切片中
		positions = append(positions, position)
	}

	return positions
}

func QueryAccountType(c *gin.Context) {
	ledgerConfig := script.GetLedgerConfigFromContext(c)
	accountTypes := script.GetLedgerAccountTypes(ledgerConfig.Id)

	result := make([]script.AccountType, 0)
	for k, v := range accountTypes {
		result = append(result, script.AccountType{Key: k, Name: v})
	}
	sort.Sort(script.AccountTypeSort(result))
	OK(c, result)
}

type AddAccountForm struct {
	Date    string `form:"date" binding:"required"`
	Account string `form:"account" binding:"required"`
	// 账户计量单位可以为空
	Currency string `form:"currency"`
}

func AddAccount(c *gin.Context) {
	var accountForm AddAccountForm
	if err := c.ShouldBindJSON(&accountForm); err != nil {
		BadRequest(c, err.Error())
		return
	}
	ledgerConfig := script.GetLedgerConfigFromContext(c)
	// 判断账户是否已存在
	accounts := script.GetLedgerAccounts(ledgerConfig.Id)
	for _, acc := range accounts {
		if acc.Acc == accountForm.Account {
			DuplicateAccount(c)
			return
		}
	}
	line := fmt.Sprintf("%s open %s %s", accountForm.Date, accountForm.Account, accountForm.Currency)
	if accountForm.Currency != "" && accountForm.Currency != ledgerConfig.OperatingCurrency {
		line += " \"FIFO\""
	}
	// 写入文件
	filePath := ledgerConfig.DataPath + "/account/" + strings.ToLower(script.GetAccountPrefix(accountForm.Account)) + ".bean"
	err := script.AppendFileInNewLine(filePath, line)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	// 更新缓存
	typ := script.GetAccountType(ledgerConfig.Id, accountForm.Account)
	account := script.Account{Acc: accountForm.Account, StartDate: accountForm.Date, Currency: accountForm.Currency, Type: &typ}
	accounts = append(accounts, account)
	script.UpdateLedgerAccounts(ledgerConfig.Id, accounts)
	OK(c, account)
}

type AddAccountTypeForm struct {
	Type string `form:"type" binding:"required"`
	Name string `form:"name" binding:"required"`
}

func AddAccountType(c *gin.Context) {
	var addAccountTypeForm AddAccountTypeForm
	if err := c.ShouldBindJSON(&addAccountTypeForm); err != nil {
		BadRequest(c, err.Error())
		return
	}
	ledgerConfig := script.GetLedgerConfigFromContext(c)
	accountTypesMap := script.GetLedgerAccountTypes(ledgerConfig.Id)
	typ := addAccountTypeForm.Type
	accountTypesMap[typ] = addAccountTypeForm.Name
	// 更新文件
	pathFile := script.GetLedgerAccountTypeFilePath(ledgerConfig.DataPath)
	bytes, err := json.Marshal(accountTypesMap)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	err = script.WriteFile(pathFile, string(bytes))
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	// 更新缓存
	script.UpdateLedgerAccountTypes(ledgerConfig.Id, accountTypesMap)
	OK(c, script.AccountType{
		Key:  addAccountTypeForm.Type,
		Name: addAccountTypeForm.Name,
	})
}

type CloseAccountForm struct {
	Date    string `form:"date" binding:"required"`
	Account string `form:"account" binding:"required"`
}

func CloseAccount(c *gin.Context) {
	var accountForm CloseAccountForm
	if err := c.ShouldBindJSON(&accountForm); err != nil {
		BadRequest(c, err.Error())
		return
	}
	ledgerConfig := script.GetLedgerConfigFromContext(c)
	line := fmt.Sprintf("%s close %s", accountForm.Date, accountForm.Account)
	// 写入文件
	filePath := ledgerConfig.DataPath + "/account/" + strings.ToLower(script.GetAccountPrefix(accountForm.Account)) + ".bean"
	err := script.AppendFileInNewLine(filePath, line)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	// 更新缓存
	accounts := script.GetLedgerAccounts(ledgerConfig.Id)
	for i := 0; i < len(accounts); i++ {
		if accounts[i].Acc == accountForm.Account {
			accounts[i].EndDate = accountForm.Date
		}
	}
	script.UpdateLedgerAccounts(ledgerConfig.Id, accounts)
	OK(c, script.Account{
		Acc: accountForm.Account, EndDate: accountForm.Date,
	})
}

func ChangeAccountIcon(c *gin.Context) {
	account := c.Query("account")
	if account == "" {
		BadRequest(c, "account is not blank")
		return
	}
	file, _ := c.FormFile("file")
	filePath := "./public/icons/" + script.GetAccountIconName(account) + ".png"
	if err := c.SaveUploadedFile(file, filePath); err != nil {
		InternalError(c, err.Error())
		// 自己完成信息提示
		return
	}
	var result = make(map[string]string)
	result["filename"] = filePath
	OK(c, result)
}

type BalanceAccountForm struct {
	Date    string `form:"date" binding:"required" json:"date"`
	Account string `form:"account" binding:"required" json:"account"`
	Number  string `form:"number" binding:"required" json:"number"`
}

func BalanceAccount(c *gin.Context) {
	var accountForm BalanceAccountForm
	if err := c.ShouldBindJSON(&accountForm); err != nil {
		BadRequest(c, err.Error())
		return
	}
	ledgerConfig := script.GetLedgerConfigFromContext(c)

	// 获取当前账户信息
	var acc script.Account
	accounts := script.GetLedgerAccounts(ledgerConfig.Id)
	for _, account := range accounts {
		if account.Acc == accountForm.Account {
			acc = account
		}
	}

	today, err := time.Parse("2006-01-02", accountForm.Date)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	todayStr := today.Format("2006-01-02")
	yesterdayStr := today.AddDate(0, 0, -1).Format("2006-01-02")
	month := today.Format("2006-01")
	line := fmt.Sprintf("\r\n%s pad %s Equity:OpeningBalances", yesterdayStr, accountForm.Account)
	line += fmt.Sprintf("\r\n%s balance %s %s %s", todayStr, accountForm.Account, accountForm.Number, acc.Currency)

	// check month bean file exist
	err = CreateMonthBeanFileIfNotExist(ledgerConfig.DataPath, month)
	if err != nil {
		if c != nil {
			InternalError(c, err.Error())
		}
		return
	}

	// append padding content to month bean file
	err = script.AppendFileInNewLine(script.GetLedgerMonthFilePath(ledgerConfig.DataPath, month), line)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	result := make(map[string]string)
	result["account"] = accountForm.Account
	result["date"] = accountForm.Date
	result["marketNumber"] = accountForm.Number
	result["marketCurrency"] = ledgerConfig.OperatingCurrency
	result["marketCurrencySymbol"] = script.GetCommoditySymbol(ledgerConfig.Id, ledgerConfig.OperatingCurrency)
	OK(c, result)
}

func RefreshAccountCache(c *gin.Context) {
	ledgerConfig := script.GetLedgerConfigFromContext(c)
	// 加载账户缓存
	err := script.LoadLedgerAccounts(ledgerConfig.Id)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	// 加载货币缓存
	err = script.LoadLedgerCurrencyMap(ledgerConfig)
	if err != nil {
		InternalError(c, err.Error())
		return
	}
	OK(c, nil)
}
