// robot.go — реализация поведения робота-автотеста НРД (MOEX МОСТ).
// Документ-источник: DOC/instruktsiya-po-testirovaniyu-s-robotom.pdf.
//
// Когда mock.Sender видит Header.ReceiverCode == RobotCode, он не
// использует default-логику (confirm/reject из Config), а формирует
// Decision по тестовому сценарию, выбранному отправителем через поле
// Data.InvestorInformation.IdentityDocument.DocumentSeries:
//
//   1111 — «Ответ с отказом». Все бумаги отвергаются с кодом ошибки,
//          выбранным по двум последним символам DocumentNumber
//          (01..09 → M2M01..M2M09).
//   2001 — «Принять все бумаги». Все бумаги подтверждаются. i-я позиция
//          в DocumentNumber определяет номер депозитария-получателя
//          (1 или 2 — реквизиты из набора депозитариев).
//   2002 — «Принять бумаги частично». i-я позиция = номер депозитария,
//          если 0 — бумага отклоняется с кодом M2M05.
//   3333 — «Выступить принимающей стороной». Робот отвергает оригинал
//          с M2M05 и (в реальности) формирует встречный M2MTransferRequest.
//          В нашем mock'е пока эмитим только первое сообщение — встречный
//          Request требует доработки приёмной стороны bj-server.

package mock

import (
	"strconv"
	"strings"

	"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
	"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml"
)

// RobotCode — код депозитария-робота НРД. Документация: «Для того, чтобы
// робот получил сообщение, код робота должен быть указан в получателях —
// Header.ReceiverCode. КОД РОБОТА: MC0012500000.»
const RobotCode m2m.DeponentCode = "MC0012500000"

// Robot-сценарии (значения DocumentSeries).
const (
	ScenarioReject       = "1111"
	ScenarioAcceptAll    = "2001"
	ScenarioAcceptPart   = "2002"
	ScenarioBeReceiver   = "3333"
)

// robotDepositary — набор тестовых реквизитов депозитариев робота из
// «Набор данных депозитариев» в инструкции. Индексация с 1.
var robotDepositary = []struct {
	INN          string
	DepCode      m2m.DeponentCode
	Account      string
	Section      string
}{
	{}, // индекс 0 — заглушка, чтобы индексация с 1 работала
	{
		INN:     "7702165310",
		DepCode: "MC0012500000",
		Account: "HL2603250011",
		Section: "31MC0012500000F00",
	},
	{
		INN:     "7702165310",
		DepCode: "MC0012500000",
		Account: "HL2603250011",
		Section: "36MC0012500000F00",
	},
}

// IsRobotTarget — true если заявка адресована роботу (по ReceiverCode).
func IsRobotTarget(req *m2m.M2MTransferRequest) bool {
	if req == nil {
		return false
	}
	return req.Header.ReceiverCode == RobotCode
}

// robotScenario извлекает выбранный сценарий из DocumentSeries.
// Если DocumentSeries не задан или содержит неизвестное значение —
// возвращает пустую строку (mock будет использовать default-логику).
func robotScenario(req *m2m.M2MTransferRequest) string {
	if req.Data.InvestorInformation.IdentityDocument.DocumentSeries == nil {
		return ""
	}
	s := string(*req.Data.InvestorInformation.IdentityDocument.DocumentSeries)
	switch s {
	case ScenarioReject, ScenarioAcceptAll, ScenarioAcceptPart, ScenarioBeReceiver:
		return s
	}
	return ""
}

// simulateRobotDecision формирует Decision согласно выбранному
// сценарию робота. Возвращает nil если ReceiverCode != RobotCode или
// DocumentSeries не задан — в этом случае caller должен пойти по
// default-логике.
func simulateRobotDecision(req *m2m.M2MTransferRequest) *m2m.M2MTransferDecision {
	if !IsRobotTarget(req) {
		return nil
	}
	scenario := robotScenario(req)
	if scenario == "" {
		return nil
	}
	docNum := string(req.Data.InvestorInformation.IdentityDocument.DocumentNumber)

	decision := &m2m.M2MTransferDecision{
		Header: m2m.DecisionHeader{
			GUID:              req.Header.GUID,
			CreationTimestamp: nsdxml.Now(),
			SenderCode:        req.Header.ReceiverCode, // робот = отправитель Decision
			ReceiverCode:      req.Header.SenderCode,
			CostInfo:          m2m.CostInfo{No: &m2m.CostInfoNo{}},
		},
		Data: m2m.DecisionData{
			ReceivingDepository: req.Data.ReceivingDepository,
		},
	}

	switch scenario {
	case ScenarioReject:
		// Все бумаги отвергаются с кодом, определённым последними 2
		// символами DocumentNumber: «01» → M2M01, «02» → M2M02 и т.д.
		errKey := lastTwoChars(docNum)
		errCode := "M2M" + errKey
		for _, sec := range req.Data.TransferredSecurities.Securities {
			decision.Data.Securities = append(decision.Data.Securities,
				m2m.DecisionSecurity{
					ReferenceID: sec.ReferenceID,
					TransferDecision: m2m.DecisionTransfer{
						Rejection: &m2m.Rejection{Codes: []string{errCode}},
					},
				})
		}

	case ScenarioAcceptAll:
		// Все бумаги подтверждаются. Депозитарий-получатель для каждой
		// секции — по позиции в DocumentNumber: i-й символ = номер
		// депозитария из robotDepositary. По умолчанию депозитарий 1.
		for i, sec := range req.Data.TransferredSecurities.Securities {
			depIdx := pickDepositary(docNum, i)
			decision.Data.Securities = append(decision.Data.Securities,
				m2m.DecisionSecurity{
					ReferenceID: sec.ReferenceID,
					TransferDecision: m2m.DecisionTransfer{
						Confirmation: &m2m.Confirmation{
							SettlementAccount: sec.SettlementAccount[0],
						},
					},
				})
			_ = depIdx // в этой версии депозитарий не подставляется в Confirmation
			// (модель Confirmation минимальна), но индекс прочитан корректно.
		}

	case ScenarioAcceptPart:
		// Частичный приём. i-я позиция = номер депозитария (1 или 2) или
		// 0 — отклонить с M2M05.
		for i, sec := range req.Data.TransferredSecurities.Securities {
			depIdx := pickDepositary(docNum, i)
			ds := m2m.DecisionSecurity{ReferenceID: sec.ReferenceID}
			if depIdx == 0 {
				ds.TransferDecision = m2m.DecisionTransfer{
					Rejection: &m2m.Rejection{Codes: []string{"M2M05"}},
				}
			} else {
				ds.TransferDecision = m2m.DecisionTransfer{
					Confirmation: &m2m.Confirmation{
						SettlementAccount: sec.SettlementAccount[0],
					},
				}
			}
			decision.Data.Securities = append(decision.Data.Securities, ds)
		}

	case ScenarioBeReceiver:
		// Отвергаем оригинальный запрос с M2M05. (Второе сообщение —
		// встречный M2MTransferRequest — будет реализовано когда у
		// bj-server появится приёмная сторона.)
		for _, sec := range req.Data.TransferredSecurities.Securities {
			decision.Data.Securities = append(decision.Data.Securities,
				m2m.DecisionSecurity{
					ReferenceID: sec.ReferenceID,
					TransferDecision: m2m.DecisionTransfer{
						Rejection: &m2m.Rejection{Codes: []string{"M2M05"}},
					},
				})
		}
	}

	return decision
}

// lastTwoChars возвращает последние 2 символа строки или "07" если строка
// короче (07 — типовой код «отказ принимающей стороны»).
func lastTwoChars(s string) string {
	if len(s) < 2 {
		return "07"
	}
	tail := s[len(s)-2:]
	// Проверим что это цифры — иначе fallback.
	if _, err := strconv.Atoi(tail); err != nil {
		return "07"
	}
	return tail
}

// pickDepositary возвращает номер депозитария (1..2 или 0 для отказа)
// из позиции i строки docNum. Цифра > длины списка → депозитарий 1.
func pickDepositary(docNum string, i int) int {
	docNum = strings.TrimSpace(docNum)
	if i >= len(docNum) {
		return 1
	}
	n, err := strconv.Atoi(docNum[i : i+1])
	if err != nil {
		return 1
	}
	if n == 0 {
		return 0
	}
	if n >= len(robotDepositary) {
		return 1
	}
	return n
}
