package realname

import (
	"context"
	"errors"
	"regexp"
	"strings"

	"hfb_sys/backend/internal/model"

	"go.uber.org/zap"
)

const (
	StatusPending  = "pending"
	StatusVerified = "verified"
	StatusRejected = "rejected"
)

var (
	ErrDependencyUnavailable = errors.New("dependency unavailable")
	ErrInvalidRealnameInput  = errors.New("invalid realname input")
	ErrProviderConfigInvalid = errors.New("realname provider config invalid")
	ErrProviderRateLimited   = errors.New("realname provider rate limited")
	ErrProviderUnavailable   = errors.New("realname provider unavailable")
)

type Service struct {
	repo     *Repository
	provider Provider
	log      *zap.Logger
}

type PublicStatus struct {
	Status          string  `json:"status"`
	Provider        string  `json:"provider,omitempty"`
	ProviderOrderNo string  `json:"provider_order_no,omitempty"`
	MaskedName      string  `json:"masked_name,omitempty"`
	MaskedIDNo      string  `json:"masked_id_no,omitempty"`
	VerifiedAt      *string `json:"verified_at,omitempty"`
	FailReason      string  `json:"fail_reason,omitempty"`
}

func NewService(repo *Repository, provider Provider, log *zap.Logger) *Service {
	return &Service{repo: repo, provider: provider, log: log}
}

func (s *Service) Start(ctx context.Context, userID uint64, name string, idNo string) (*model.UserRealname, error) {
	if s.repo == nil || s.provider == nil {
		return nil, ErrDependencyUnavailable
	}
	name = strings.TrimSpace(name)
	idNo = strings.TrimSpace(idNo)
	if !validName(name) || !validIDNo(idNo) {
		return nil, ErrInvalidRealnameInput
	}
	result, err := s.provider.Start(ctx, StartRequest{
		UserID: userID,
		Name:   name,
		IDNo:   idNo,
	})
	if err != nil {
		if s.log != nil {
			s.log.Warn(
				"realname provider verify failed",
				zap.String("provider", s.provider.Name()),
				zap.Uint64("user_id", userID),
				zap.String("masked_id_no", maskIDNo(idNo)),
				zap.Error(err),
			)
		}
		return nil, err
	}
	return s.repo.SaveResult(userID, s.provider.Name(), result)
}

func (s *Service) Status(userID uint64) (PublicStatus, error) {
	if s.repo == nil {
		return PublicStatus{}, ErrDependencyUnavailable
	}
	record, err := s.repo.FindByUserID(userID)
	if err != nil {
		return PublicStatus{Status: "unverified"}, nil
	}
	return ToPublicStatus(record), nil
}

func ToPublicStatus(record *model.UserRealname) PublicStatus {
	var verifiedAt *string
	if record.VerifiedAt != nil {
		value := record.VerifiedAt.Format("2006-01-02 15:04:05")
		verifiedAt = &value
	}
	return PublicStatus{
		Status:          record.Status,
		Provider:        record.Provider,
		ProviderOrderNo: record.ProviderOrderNo,
		MaskedName:      record.MaskedName,
		MaskedIDNo:      record.MaskedIDNo,
		VerifiedAt:      verifiedAt,
		FailReason:      record.FailReason,
	}
}

func validName(name string) bool {
	return len([]rune(name)) >= 2 && len([]rune(name)) <= 30
}

func validIDNo(idNo string) bool {
	return regexp.MustCompile(`^\d{17}[\dXx]$`).MatchString(idNo)
}
