package account

import (
	"api_yal/internal/logger"
	"api_yal/internal/models"
	"api_yal/internal/repository"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"time"

	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

// AccountService интерфейс сервиса аккаунтов
type AccountService interface {
	// Основные операции
	GetAccountByID(id uint) (*AccountResponse, error)
	GetAccountByEmail(email string) (*AccountResponse, error)
	GetAccountWithObjects(id uint) (*AccountWithObjectsResponse, error)
	UpdateAccount(id uint, req UpdateAccountRequest) (*AccountResponse, error)
	DeleteAccount(id uint) error

	// Управление паролем
	ChangePassword(userID uint, req ChangePasswordRequest) error

	// Административные функции
	ListAccounts(req ListAccountsRequest) (*AccountListResponse, error)
	VerifyAccount(accountID uint, req VerifyAccountRequest) error
	UpdateAccountStatus(accountID uint, req UpdateAccountStatusRequest) error

	// Статистика
	GetAccountStats(userID uint) (*AccountStats, error)
	GetAccountProfile(userID uint) (*AccountProfileResponse, error)

	// Внутренние методы для auth сервиса
	GetAccountModelByID(id uint) (*models.Account, error)
	GetAccountModelByEmail(email string) (*models.Account, error)
	CreateAccount(req CreateAccountRequest) (*models.Account, error)
	UpdateAccountModel(account *models.Account) error
}

// accountServiceImpl реализация сервиса аккаунтов
type accountServiceImpl struct {
	accountRepo repository.AccountRepository
}

// NewService создает новый экземпляр сервиса аккаунтов
func NewService(accountRepo repository.AccountRepository) AccountService {
	return &accountServiceImpl{
		accountRepo: accountRepo,
	}
}

// GetAccountByID получает аккаунт по ID
func (s *accountServiceImpl) GetAccountByID(id uint) (*AccountResponse, error) {
	l := logger.Get()
	l.Debug("Получение аккаунта по ID", zap.Uint("id", id))

	account, err := s.accountRepo.GetByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrAccountNotFound
		}
		l.Error("Ошибка получения аккаунта", zap.Error(err))
		return nil, err
	}

	response := ToAccountResponse(account)
	return &response, nil
}

// GetAccountByEmail получает аккаунт по email
func (s *accountServiceImpl) GetAccountByEmail(email string) (*AccountResponse, error) {
	l := logger.Get()
	l.Debug("Получение аккаунта по email", zap.String("email", email))

	account, err := s.accountRepo.GetByEmail(email)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrAccountNotFound
		}
		l.Error("Ошибка получения аккаунта", zap.Error(err))
		return nil, err
	}

	response := ToAccountResponse(account)
	return &response, nil
}

// GetAccountWithObjects получает аккаунт с его объектами
func (s *accountServiceImpl) GetAccountWithObjects(id uint) (*AccountWithObjectsResponse, error) {
	l := logger.Get()
	l.Debug("Получение аккаунта с объектами", zap.Uint("id", id))

	account, err := s.accountRepo.GetByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrAccountNotFound
		}
		l.Error("Ошибка получения аккаунта", zap.Error(err))
		return nil, err
	}

	objects, err := s.accountRepo.GetObjects(id)
	if err != nil {
		l.Error("Ошибка получения объектов", zap.Error(err))
		return nil, err
	}
	account.Objects = objects

	response := ToAccountWithObjectsResponse(account)
	return &response, nil
}

// UpdateAccount обновляет информацию об аккаунте
func (s *accountServiceImpl) UpdateAccount(id uint, req UpdateAccountRequest) (*AccountResponse, error) {
	l := logger.Get()
	l.Info("Обновление аккаунта", zap.Uint("id", id))

	account, err := s.accountRepo.GetByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrAccountNotFound
		}
		return nil, err
	}

	// Обновляем поля
	if req.FullName != "" {
		account.FullName = req.FullName
	}
	if req.FirstName != "" {
		account.FirstName = req.FirstName
	}
	if req.LastName != "" {
		account.LastName = req.LastName
	}
	if req.Phone != "" {
		account.Phone = req.Phone
	}
	if req.City != "" {
		account.City = req.City
	}
	if req.OrganizationForm != "" {
		account.OrganizationForm = req.OrganizationForm
	}
	if req.OrganizationName != "" {
		account.OrganizationName = req.OrganizationName
	}
	if req.OrganizationShort != "" {
		account.OrganizationShort = req.OrganizationShort
	}
	if req.INN != "" {
		account.INN = req.INN
	}
	if req.PersonalINN != "" {
		account.PersonalINN = req.PersonalINN
	}

	if err := s.accountRepo.Update(account); err != nil {
		l.Error("Ошибка обновления аккаунта", zap.Error(err))
		return nil, err
	}

	response := ToAccountResponse(account)
	return &response, nil
}

// DeleteAccount удаляет аккаунт (мягкое удаление)
func (s *accountServiceImpl) DeleteAccount(id uint) error {
	l := logger.Get()
	l.Info("Удаление аккаунта", zap.Uint("id", id))

	if err := s.accountRepo.Delete(id); err != nil {
		l.Error("Ошибка удаления аккаунта", zap.Error(err))
		return err
	}

	return nil
}

// ChangePassword изменяет пароль пользователя
func (s *accountServiceImpl) ChangePassword(userID uint, req ChangePasswordRequest) error {
	l := logger.Get()
	l.Info("Смена пароля", zap.Uint("userID", userID))

	account, err := s.accountRepo.GetByID(userID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrAccountNotFound
		}
		return err
	}

	// Проверяем текущий пароль
	if err := bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(req.CurrentPassword)); err != nil {
		l.Error("Неверный текущий пароль")
		return ErrInvalidCurrentPassword
	}

	// Хешируем новый пароль
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
	if err != nil {
		l.Error("Ошибка хеширования пароля", zap.Error(err))
		return err
	}

	account.PasswordHash = string(hashedPassword)
	if err := s.accountRepo.Update(account); err != nil {
		l.Error("Ошибка обновления пароля", zap.Error(err))
		return err
	}

	l.Info("Пароль успешно изменен", zap.Uint("userID", userID))
	return nil
}

// ForgotPassword запрашивает сброс пароля
func (s *accountServiceImpl) ForgotPassword(email string) (string, error) {
	l := logger.Get()
	l.Info("Запрос сброса пароля", zap.String("email", email))

	account, err := s.accountRepo.GetByEmail(email)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Для безопасности не сообщаем, что пользователь не найден
			return "", nil
		}
		return "", err
	}

	// Генерируем reset token (используем метод из auth сервиса)
	// В реальном приложении здесь должна быть генерация токена
	resetToken, err := generateResetToken()
	if err != nil {
		l.Error("Ошибка генерации reset token", zap.Error(err))
		return "", err
	}

	passwordReset := &models.PasswordReset{
		AccountID: account.ID,
		Token:     resetToken,
		ExpiresAt: time.Now().Add(1 * time.Hour),
		Used:      false,
	}

	if err := s.accountRepo.CreatePasswordReset(passwordReset); err != nil {
		l.Error("Ошибка сохранения reset token", zap.Error(err))
		return "", err
	}

	l.Info("Reset token создан", zap.String("email", email))
	return resetToken, nil
}

// ResetPassword сбрасывает пароль по токену
func (s *accountServiceImpl) ResetPassword(token, newPassword string) error {
	l := logger.Get()
	l.Info("Сброс пароля по токену")

	passwordReset, err := s.accountRepo.GetPasswordResetByToken(token)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrResetTokenNotFound
		}
		return err
	}

	// Проверяем срок действия токена
	if passwordReset.ExpiresAt.Before(time.Now()) {
		return ErrResetTokenExpired
	}

	// Проверяем, не использован ли токен
	if passwordReset.Used {
		return ErrResetTokenAlreadyUsed
	}

	// Получаем пользователя
	account, err := s.accountRepo.GetByID(passwordReset.AccountID)
	if err != nil {
		return ErrAccountNotFound
	}

	// Хешируем новый пароль
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		l.Error("Ошибка хеширования пароля", zap.Error(err))
		return err
	}

	// Обновляем пароль
	account.PasswordHash = string(hashedPassword)
	if err := s.accountRepo.Update(account); err != nil {
		l.Error("Ошибка обновления пароля", zap.Error(err))
		return err
	}

	// Помечаем токен как использованный
	passwordReset.Used = true
	if err := s.accountRepo.UpdatePasswordReset(passwordReset); err != nil {
		l.Error("Ошибка обновления статуса токена", zap.Error(err))
	}

	l.Info("Пароль успешно сброшен", zap.Uint("userID", account.ID))
	return nil
}

// ListAccounts возвращает список аккаунтов с пагинацией
func (s *accountServiceImpl) ListAccounts(req ListAccountsRequest) (*AccountListResponse, error) {
	l := logger.Get()
	l.Debug("Получение списка аккаунтов", zap.Any("request", req))

	// Устанавливаем значения по умолчанию
	if req.Page == 0 {
		req.Page = 1
	}
	if req.PageSize == 0 {
		req.PageSize = 20
	}

	offset := (req.Page - 1) * req.PageSize

	var accounts []models.Account
	var total int64
	var err error

	// Поиск с фильтрацией
	if req.Search != "" {
		accounts, err = s.accountRepo.Search(req.Search, offset, req.PageSize)
		if err != nil {
			l.Error("Ошибка поиска аккаунтов", zap.Error(err))
			return nil, err
		}
		total, err = s.getSearchTotal(req.Search)
	} else {
		accounts, err = s.accountRepo.List(offset, req.PageSize)
		if err != nil {
			l.Error("Ошибка получения списка аккаунтов", zap.Error(err))
			return nil, err
		}
		total, err = s.accountRepo.Count()
	}

	if err != nil {
		return nil, err
	}

	// Фильтруем по роли и статусу (если нужно)
	filteredAccounts := s.filterAccounts(accounts, req.Role, req.IsActive)

	items := make([]AccountResponse, len(filteredAccounts))
	for i, acc := range filteredAccounts {
		items[i] = ToAccountResponse(&acc)
	}

	totalPages := int(total) / req.PageSize
	if int(total)%req.PageSize > 0 {
		totalPages++
	}

	return &AccountListResponse{
		Items:      items,
		Total:      total,
		Page:       req.Page,
		PageSize:   req.PageSize,
		TotalPages: totalPages,
	}, nil
}

// VerifyAccount верифицирует аккаунт
func (s *accountServiceImpl) VerifyAccount(accountID uint, req VerifyAccountRequest) error {
	l := logger.Get()
	l.Info("Верификация аккаунта", zap.Uint("id", accountID))

	account, err := s.accountRepo.GetByID(accountID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrAccountNotFound
		}
		return err
	}

	account.IsVerified = req.IsVerified
	if err := s.accountRepo.Update(account); err != nil {
		l.Error("Ошибка обновления статуса верификации", zap.Error(err))
		return err
	}

	l.Info("Аккаунт верифицирован", zap.Uint("id", accountID))
	return nil
}

// UpdateAccountStatus обновляет статус аккаунта (админ)
func (s *accountServiceImpl) UpdateAccountStatus(accountID uint, req UpdateAccountStatusRequest) error {
	l := logger.Get()
	l.Info("Обновление статуса аккаунта", zap.Uint("id", accountID))

	account, err := s.accountRepo.GetByID(accountID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrAccountNotFound
		}
		return err
	}

	account.IsActive = req.IsActive
	if req.Role != "" {
		account.Role = req.Role
	}

	if err := s.accountRepo.Update(account); err != nil {
		l.Error("Ошибка обновления статуса аккаунта", zap.Error(err))
		return err
	}

	l.Info("Статус аккаунта обновлен", zap.Uint("id", accountID))
	return nil
}

// GetAccountStats получает статистику аккаунта
func (s *accountServiceImpl) GetAccountStats(userID uint) (*AccountStats, error) {
	l := logger.Get()
	l.Debug("Получение статистики аккаунта", zap.Uint("userID", userID))

	// Здесь должна быть реальная логика подсчета статистики
	// В реальном приложении нужно запрашивать данные из соответствующих репозиториев

	stats := &AccountStats{
		ObjectsCount:   0,
		FeedbacksCount: 0,
		CommentsCount:  0,
		RatingsCount:   0,
		AppealsCount:   0,
	}

	// TODO: Получить реальную статистику из базы данных

	return stats, nil
}

// GetAccountProfile получает профиль пользователя со статистикой
func (s *accountServiceImpl) GetAccountProfile(userID uint) (*AccountProfileResponse, error) {
	l := logger.Get()
	l.Debug("Получение профиля пользователя", zap.Uint("userID", userID))

	account, err := s.accountRepo.GetByID(userID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrAccountNotFound
		}
		return nil, err
	}

	stats, err := s.GetAccountStats(userID)
	if err != nil {
		l.Error("Ошибка получения статистики", zap.Error(err))
		return nil, err
	}

	return &AccountProfileResponse{
		AccountResponse: ToAccountResponse(account),
		Stats:           *stats,
	}, nil
}

// GetAccountModelByID получает модель аккаунта по ID (для внутреннего использования)
func (s *accountServiceImpl) GetAccountModelByID(id uint) (*models.Account, error) {
	return s.accountRepo.GetByID(id)
}

// GetAccountModelByEmail получает модель аккаунта по email (для внутреннего использования)
func (s *accountServiceImpl) GetAccountModelByEmail(email string) (*models.Account, error) {
	return s.accountRepo.GetByEmail(email)
}

// CreateAccount создает новый аккаунт
func (s *accountServiceImpl) CreateAccount(req CreateAccountRequest) (*models.Account, error) {
	l := logger.Get()
	l.Info("Создание аккаунта", zap.String("email", req.Email))

	// Проверяем, существует ли пользователь
	existing, _ := s.accountRepo.GetByEmail(req.Email)
	if existing != nil {
		return nil, ErrAccountAlreadyExists
	}

	// Хешируем пароль
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		l.Error("Ошибка хеширования пароля", zap.Error(err))
		return nil, err
	}

	// Формируем полное имя, если не указано
	fullName := req.FullName
	if fullName == "" {
		fullName = req.FirstName + " " + req.LastName
	}

	account := &models.Account{
		Email:             req.Email,
		PasswordHash:      string(hashedPassword),
		FullName:          fullName,
		FirstName:         req.FirstName,
		LastName:          req.LastName,
		Phone:             req.Phone,
		City:              req.City,
		OrganizationForm:  req.OrganizationForm,
		OrganizationName:  req.OrganizationName,
		OrganizationShort: req.OrganizationShort,
		INN:               req.INN,
		PersonalINN:       req.PersonalINN,
		IsActive:          true,
		IsVerified:        false,
		Role:              "user",
	}

	if err := s.accountRepo.Create(account); err != nil {
		l.Error("Ошибка создания аккаунта", zap.Error(err))
		return nil, err
	}

	l.Info("Аккаунт создан", zap.String("email", req.Email))
	return account, nil
}

// UpdateAccountModel обновляет модель аккаунта
func (s *accountServiceImpl) UpdateAccountModel(account *models.Account) error {
	return s.accountRepo.Update(account)
}

// Вспомогательные методы

func (s *accountServiceImpl) getSearchTotal(query string) (int64, error) {
	fmt.Printf("query string = %s", query)
	// Здесь должна быть реализация подсчета общего количества результатов поиска
	// Для простоты возвращаем 0
	return 0, nil
}

func (s *accountServiceImpl) filterAccounts(accounts []models.Account, role string, isActive *bool) []models.Account {
	var filtered []models.Account

	for _, acc := range accounts {
		if role != "" && acc.Role != role {
			continue
		}
		if isActive != nil && acc.IsActive != *isActive {
			continue
		}
		filtered = append(filtered, acc)
	}

	return filtered
}

// Генерация reset токена
func generateResetToken() (string, error) {
	// Генерируем 32 байта (64 символа в hex) — достаточно для безопасности
	bytes := make([]byte, 32)
	_, err := rand.Read(bytes)
	if err != nil {
		return "", fmt.Errorf("failed to generate token: %w", err)
	}
	return hex.EncodeToString(bytes), nil
}
