package requestmodel

import (
	"fmt"
	"net/url"
	"time"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
	dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
)

type (
	Balance struct {
		RawBalance   int64 `json:"raw_balance"`
		CleanBalance int64 `json:"clean_balance"`
	}
)

func (b *Balance) From(msg *dbtypes.Balance) *Balance {
	if msg == nil {
		return nil
	}

	res := &Balance{
		RawBalance:   msg.RawBalance,
		CleanBalance: msg.CleanBalance,
	}

	return res
}

type (
	BalanceGetRequest struct {
		OwnerId string
	}

	BalanceGetResponse struct {
		Balance *Balance `json:"balance"`
	}
)

func (r *BalanceGetRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	return nil
}

func (r *BalanceGetResponse) From(msg *dbtypes.BalanceGetResponse) *BalanceGetResponse {
	if msg == nil {
		return nil
	}

	return &BalanceGetResponse{
		Balance: new(Balance).From(msg.Balance),
	}
}

type TransactionStatus string

const (
	TransactionStatusNew      TransactionStatus = "new"
	TransactionStatusPending  TransactionStatus = "pending"
	TransactionStatusApproved TransactionStatus = "approved"
	TransactionStatusRejected TransactionStatus = "rejected"
)

func (s *TransactionStatus) NullString() *string {
	if s == nil {
		return nil
	}

	return (*string)(s)
}

type TransactionType string

const (
	TransactionTypeDeposit    TransactionType = "deposit"
	TransactionTypeWithdrawal TransactionType = "withdrawal"
)

func (t *TransactionType) NullString() *string {
	if t == nil {
		return nil
	}

	return (*string)(t)
}

type (
	Transaction struct {
		Id              string                `json:"id"`
		OwnerInfo       *TransactionOwnerInfo `json:"owner_info"`
		BankAccountInfo *BankAccountInfo      `json:"bank_account_info"`
		Type            TransactionType       `json:"type"`
		Status          TransactionStatus     `json:"status"`
		Amount          int64                 `json:"amount"`
		Currency        string                `json:"currency"`
		CreatedAt       string                `json:"created_at"`
		Payload         *TransactionPayload   `json:"payload"`
	}

	BankAccountInfo struct {
		Id                   string `json:"id"`
		AccountNumber        string `json:"account_number"`
		AccountName          string `json:"account_name"`
		BankName             string `json:"bank_name"`
		Bik                  string `json:"bik"`
		CorrespondentAccount string `json:"correspondent_account"`
		OwnerType            string `json:"owner_type"`
	}

	TransactionOwnerInfo struct {
		Id   string `json:"id"`
		Name string `json:"name"`
	}

	TransactionPayload struct {
		Origin      string `json:"origin"`
		CompanyId   string `json:"company_id"`
		CompanyName string `json:"company_name"`
		VacancyId   string `json:"vacancy_id"`
		VacancyName string `json:"vacancy_name"`
	}
)

func (t *TransactionOwnerInfo) From(msg *dbtypes.TransactionOwnerInfo) *TransactionOwnerInfo {
	if msg == nil {
		return nil
	}

	res := &TransactionOwnerInfo{
		Id:   msg.Id,
		Name: msg.Name,
	}

	return res
}

func (t *BankAccountInfo) From(msg *dbtypes.BankAccountInfo) *BankAccountInfo {
	if msg == nil {
		return nil
	}

	res := &BankAccountInfo{
		Id:                   msg.Id,
		AccountNumber:        msg.AccountNumber,
		AccountName:          msg.AccountName,
		BankName:             msg.BankName,
		Bik:                  msg.Bik,
		CorrespondentAccount: msg.CorrespondentAccount,
		OwnerType:            msg.OwnerType,
	}

	return res
}

func (t *TransactionPayload) From(msg *dbtypes.TransactionPayload) *TransactionPayload {
	if msg == nil {
		return nil
	}

	res := &TransactionPayload{
		Origin:      msg.Origin,
		CompanyId:   msg.CompanyId,
		CompanyName: msg.CompanyName,
		VacancyId:   msg.VacancyId,
		VacancyName: msg.VacancyName,
	}

	return res
}

func (t *Transaction) From(msg *dbtypes.Transaction) *Transaction {
	if msg == nil {
		return nil
	}

	res := &Transaction{
		Id:              msg.Id,
		OwnerInfo:       new(TransactionOwnerInfo).From(msg.OwnerInfo),
		BankAccountInfo: new(BankAccountInfo).From(msg.BankAccountInfo),
		Type:            TransactionType(msg.Type),
		Status:          TransactionStatus(msg.Status),
		Amount:          msg.Amount,
		Currency:        msg.Currency,
		CreatedAt:       msg.CreatedAt.Format(time.RFC3339),
		Payload:         new(TransactionPayload).From(msg.Payload),
	}

	return res
}

type (
	TransactionListGetRequest struct {
		OwnerId  string
		Filters  *TransactionListFilters
		Page     uint64
		PageSize uint64
	}

	TransactionListFilters struct {
		Type          *TransactionType
		Status        *TransactionStatus
		BankAccountId *string
	}

	TransactionListGetResponse struct {
		Transactions []Transaction `json:"transactions"`
	}
)

func (r *TransactionListGetRequest) FromQuery(query url.Values) (*TransactionListGetRequest, error) {
	if query == nil {
		return nil, nil
	}

	var filters TransactionListFilters

	if val, ok := query["type"]; ok {
		typ := TransactionType(val[0])
		filters.Type = &typ
	}

	if val, ok := query["status"]; ok {
		status := TransactionStatus(val[0])
		filters.Status = &status
	}

	if val, ok := query["bank_account_id"]; ok {
		filters.BankAccountId = &val[0]
	}

	res := &TransactionListGetRequest{
		Filters:  &filters,
		Page:     constants.DefaultPaginationPage,
		PageSize: constants.DefaultPaginationPageSize,
	}

	if val, ok := query["page"]; ok {
		intVal, err := parseUint64(val[0])
		if err != nil {
			return nil, fmt.Errorf("invalid page param: %w", err)
		}

		res.Page = intVal
	}

	if val, ok := query["page_size"]; ok {
		intVal, err := parseUint64(val[0])
		if err != nil {
			return nil, fmt.Errorf("invalid page_size param: %w", err)
		}

		res.PageSize = intVal
	}

	return res, nil
}

func (r *TransactionListGetRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	if r.Filters != nil {
		if r.Filters.Type != nil && !(*r.Filters.Type == TransactionTypeDeposit ||
			*r.Filters.Type == TransactionTypeWithdrawal) {
			return fmt.Errorf("%w: invalid type: %v", ErrValidation, *r.Filters.Type)
		}

		if r.Filters.Status != nil && !(*r.Filters.Status == TransactionStatusNew ||
			*r.Filters.Status == TransactionStatusPending ||
			*r.Filters.Status == TransactionStatusApproved ||
			*r.Filters.Status == TransactionStatusRejected) {
			return fmt.Errorf("%w: invalid status: %v", ErrValidation, *r.Filters.Status)
		}
	}

	return nil
}

func (r *TransactionListGetResponse) From(msg *dbtypes.TransactionListGetResponse) *TransactionListGetResponse {
	if msg == nil {
		return nil
	}

	res := &TransactionListGetResponse{
		Transactions: make([]Transaction, len(msg.Transactions)),
	}

	for i, trans := range msg.Transactions {
		res.Transactions[i] = *new(Transaction).From(&trans)
	}

	return res
}

type (
	TransactionCreateRequest struct {
		OwnerId       string
		Amount        int64               `json:"amount"`
		Currency      string              `json:"currency"`
		BankAccountId *string             `json:"bank_account_id"`
		Payload       *TransactionPayload `json:"payload"`
		RequestId     string              `json:"request_id"`
	}

	TransactionCreateResponse struct {
		Id string `json:"id"`
	}
)

func (r *TransactionCreateRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.RequestId == "" {
		return fmt.Errorf("%w: RequestID is required", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	if r.Amount == 0 {
		return fmt.Errorf("%w: Amount is required", ErrValidation)
	}

	if r.Currency == "" {
		return fmt.Errorf("%w: Currency is required", ErrValidation)
	}

	return nil
}

func (r *TransactionCreateResponse) From(msg *dbtypes.TransactionCreateResponse) *TransactionCreateResponse {
	if msg == nil {
		return nil
	}

	return &TransactionCreateResponse{
		Id: msg.Id,
	}
}

type (
	BankAccount struct {
		Id                   string `json:"id"`
		OwnerId              string `json:"owner_id"`
		AccountNumber        string `json:"account_number"`
		AccountName          string `json:"account_name"`
		BankName             string `json:"bank_name"`
		Bik                  string `json:"bik"`
		CorrespondentAccount string `json:"correspondent_account"`
		CreatedAt            string `json:"created_at"`
		UpdatedAt            string `json:"updated_at"`
	}
)

func (b *BankAccount) From(msg *dbtypes.BankAccount) *BankAccount {
	if msg == nil {
		return nil
	}

	res := &BankAccount{
		Id:                   msg.Id,
		OwnerId:              msg.OwnerId,
		AccountNumber:        msg.AccountNumber,
		AccountName:          msg.AccountName,
		BankName:             msg.BankName,
		Bik:                  msg.Bik,
		CorrespondentAccount: msg.CorrespondentAccount,
		CreatedAt:            msg.CreatedAt.Format(time.RFC3339),
		UpdatedAt:            msg.UpdatedAt.Format(time.RFC3339),
	}

	return res
}

type (
	BankAccountListGetRequest struct {
		OwnerId string
	}

	BankAccountListGetResponse struct {
		BankAccounts []BankAccount `json:"bank_accounts"`
	}
)

func (r *BankAccountListGetRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	return nil
}

func (r *BankAccountListGetResponse) From(msg *dbtypes.BankAccountListGetResponse) *BankAccountListGetResponse {
	if msg == nil {
		return nil
	}

	res := &BankAccountListGetResponse{
		BankAccounts: make([]BankAccount, len(msg.BankAccounts)),
	}

	for i, bankAccount := range msg.BankAccounts {
		res.BankAccounts[i] = *new(BankAccount).From(&bankAccount)
	}

	return res
}

type (
	BankAccountCreateRequest struct {
		OwnerId              string
		AccountNumber        *string `json:"account_number"`
		AccountName          *string `json:"account_name"`
		BankName             *string `json:"bank_name"`
		Bik                  *string `json:"bik"`
		CorrespondentAccount *string `json:"correspondent_account"`
		IsPrimary            *bool   `json:"is_primary"`
	}

	BankAccountCreateResponse struct {
		Id string `json:"id"`
	}
)

func (r *BankAccountCreateRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	return nil
}

func (r *BankAccountCreateResponse) From(msg *dbtypes.BankAccountCreateResponse) *BankAccountCreateResponse {
	if msg == nil {
		return nil
	}

	return &BankAccountCreateResponse{
		Id: msg.Id,
	}
}

type (
	BankAccountUpdateRequest struct {
		Id                   string
		OwnerId              string
		AccountNumber        *string `json:"account_number"`
		AccountName          *string `json:"account_name"`
		BankName             *string `json:"bank_name"`
		Bik                  *string `json:"bik"`
		CorrespondentAccount *string `json:"correspondent_account"`
		IsPrimary            *bool   `json:"is_primary"`
	}

	BankAccountUpdateResponse struct{}
)

func (r *BankAccountUpdateRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.Id == "" {
		return fmt.Errorf("%w: ID is required", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	if r.AccountNumber == nil && r.AccountName == nil && r.BankName == nil && r.Bik == nil && r.CorrespondentAccount == nil {
		return fmt.Errorf("%w: nothing to update", ErrValidation)
	}

	return nil
}

type (
	BankAccountDeleteRequest struct {
		Id      string
		OwnerId string
	}

	BankAccountDeleteResponse struct{}
)

func (r *BankAccountDeleteRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.Id == "" {
		return fmt.Errorf("%w: ID is required", ErrValidation)
	}

	if r.OwnerId == "" {
		return fmt.Errorf("%w: OwnerID is required", ErrValidation)
	}

	return nil
}
