package m2mcore

import (
	"context"
	"crypto/rand"
	"fmt"
	"math/big"
	"time"

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

// ClaimInput — входная заявка из ЛК (после OpenAPI-парсинга).
type ClaimInput struct {
	InvestorClientID          string
	TransferringDepositoryINN m2m.OrganizationINN
	ReceivingDepositoryINN    m2m.OrganizationINN
	CostInfo                  m2m.CostInfo
	IIAAgreement              *m2m.IIAAgreementDetails
	Securities                []ClaimSecurityInput
	// InvestorDocument, если задан, переопределяет документ инвестора из
	// анкеты Fansy. Нужен для тестов с роботом НРД, где код сценария
	// кодируется в серии ДУЛ (DocumentSeries). Для обычных заявок — nil
	// (личность берётся из анкеты-источника).
	InvestorDocument *ClientDocument
}

// ClaimSecurityInput — одна ЦБ в заявке.
type ClaimSecurityInput struct {
	SecurityCode m2m.SecurityCode
	Details      m2m.SecurityDetails
	Quantity     m2m.Quantity
}

// SenderReceiver — отправитель и получатель в Header (коды депонентов).
type SenderReceiver struct {
	SenderCode   m2m.DeponentCode
	ReceiverCode m2m.DeponentCode
}

// referenceIDChars — алфавит для генерации ReferenceID (5 случайных
// символов после префикса "M2M" и даты).
const referenceIDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

// NewReferenceID генерирует идентификатор операции в формате
// "M2M" + YYYYMMDD + 5 случайных символов из [A-Z0-9].
// Длина — ровно 16, как требует XSD ReferenceIdType.
func NewReferenceID(at time.Time) (m2m.ReferenceID, error) {
	suffix := make([]byte, 5)
	for i := range suffix {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(referenceIDChars))))
		if err != nil {
			return "", fmt.Errorf("m2mcore: NewReferenceID rand: %w", err)
		}
		suffix[i] = referenceIDChars[n.Int64()]
	}
	day := at.UTC()
	id := fmt.Sprintf("M2M%04d%02d%02d%s",
		day.Year(), int(day.Month()), day.Day(), suffix)
	out := m2m.ReferenceID(id)
	if err := out.Validate(); err != nil {
		return "", err
	}
	return out, nil
}

// EnrichRequest собирает M2MTransferRequest из заявки ЛК и данных Fansy.
// Шаги:
//  1. Поднять анкету клиента.
//  2. Поднять депо-счета у передающего депозитария и проверить остатки.
//  3. Сгенерировать GUID, ReferenceID для каждой ЦБ, CreationTimestamp.
//  4. Заполнить структуру и провалидировать.
func EnrichRequest(
	ctx context.Context,
	store FansyStore,
	claim ClaimInput,
	codes SenderReceiver,
) (*m2m.M2MTransferRequest, error) {
	client, err := store.GetClientByID(ctx, claim.InvestorClientID)
	if err != nil {
		return nil, fmt.Errorf("m2mcore: GetClientByID: %w", err)
	}
	// Переопределение документа инвестора (тест с роботом: серия ДУЛ = код
	// сценария). Меняем только удостоверение личности, ФИО оставляем из анкеты.
	if claim.InvestorDocument != nil {
		client.Document = *claim.InvestorDocument
	}
	accounts, err := store.GetDepoAccounts(ctx, claim.InvestorClientID, claim.TransferringDepositoryINN)
	if err != nil {
		return nil, fmt.Errorf("m2mcore: GetDepoAccounts: %w", err)
	}
	if len(accounts) == 0 {
		return nil, fmt.Errorf("m2mcore: у клиента нет активных счетов в передающем депозитарии")
	}

	guid, err := NewUUIDv4()
	if err != nil {
		return nil, err
	}
	now := time.Now()

	securities := make([]m2m.RequestSecurity, 0, len(claim.Securities))
	for _, sec := range claim.Securities {
		refID, err := NewReferenceID(now)
		if err != nil {
			return nil, err
		}
		// Берём первый активный счёт как минимум; реальная логика выбора
		// settlement_accounts будет в M2 (по типу ЦБ и торговому разделу).
		settlement := make([]m2m.RequestSettlementAccount, 0, len(accounts))
		for _, a := range accounts {
			settlement = append(settlement, m2m.RequestSettlementAccount{
				SettlementRequisites: m2m.SettlementRequisites{INN: a.DepositoryINN},
				SettlementLocation: m2m.SettlementDepositoryLocation{
					DeponentCode: a.DeponentCode,
					AccountID:    a.AccountID,
					SectionID:    a.SectionID,
				},
			})
		}
		securities = append(securities, m2m.RequestSecurity{
			ReferenceID:       refID,
			SecurityCode:      sec.SecurityCode,
			SecurityDetails:   sec.Details,
			Quantity:          sec.Quantity,
			SettlementAccount: settlement,
			IsolationStatus:   m2m.IsolationSGDN,
		})
	}

	req := &m2m.M2MTransferRequest{
		Header: m2m.RequestHeader{
			GUID:                m2m.UUID(guid),
			CreationTimestamp:   nsdxml.Now(),
			SenderCode:          codes.SenderCode,
			ReceiverCode:        codes.ReceiverCode,
			CostInfo:            claim.CostInfo,
			IIAAgreementDetails: claim.IIAAgreement,
		},
		Data: m2m.RequestData{
			InvestorInformation: m2m.InvestorInformation{
				LastName:   client.LastName,
				FirstName:  client.FirstName,
				MiddleName: client.MiddleName,
				IdentityDocument: m2m.IdentityDocument{
					DocumentType:   client.Document.DocumentType,
					DocumentNumber: m2m.IdentityDocSerial(client.Document.Number),
				},
			},
			TransferringDepository: m2m.SettlementRequisites{INN: claim.TransferringDepositoryINN},
			ReceivingDepository:    m2m.SettlementRequisites{INN: claim.ReceivingDepositoryINN},
			TransferredSecurities:  m2m.RequestTransferredSecurities{Securities: securities},
		},
	}
	if claim.IIAAgreement != nil {
		req.Header.IIAAgreementDetails = claim.IIAAgreement
	}
	if client.Document.Series != "" {
		series := m2m.IdentityDocSerial(client.Document.Series)
		req.Data.InvestorInformation.IdentityDocument.DocumentSeries = &series
	}
	if err := req.Validate(); err != nil {
		return nil, fmt.Errorf("m2mcore: собранный M2MTransferRequest невалиден: %w", err)
	}
	return req, nil
}
