package auth

import (
	"context"
	"errors"
	"time"

	"github.com/google/uuid"

	"nexavpn/backend/internal/identity"
)

var ErrInvalidCredentials = errors.New("invalid credentials")

type UserRecord struct {
	ID           uuid.UUID
	Username     string
	DisplayName  string
	Role         string
	PasswordHash string
	IsActive     bool
}

type Repository interface {
	FindUserByUsername(ctx context.Context, username string) (UserRecord, error)
	CreateSession(ctx context.Context, userID uuid.UUID, expiresAt time.Time, ipAddress string, userAgent string) (uuid.UUID, error)
	StoreRefreshToken(ctx context.Context, sessionID uuid.UUID, userID uuid.UUID, tokenHash string, expiresAt time.Time) error
	FindRefreshToken(ctx context.Context, tokenHash string) (UserRecord, uuid.UUID, error)
	RevokeRefreshToken(ctx context.Context, tokenHash string) error
	HasUsers(ctx context.Context) (bool, error)
	CreateBootstrapAdmin(ctx context.Context, username, displayName, passwordHash string) (UserRecord, error)
}

type Service struct {
	repo            Repository
	jwtSecret       string
	jwtIssuer       string
	accessTokenTTL  time.Duration
	refreshTokenTTL time.Duration
}

func NewService(repo Repository, jwtSecret, jwtIssuer string, accessTokenTTL, refreshTokenTTL time.Duration) *Service {
	return &Service{
		repo:            repo,
		jwtSecret:       jwtSecret,
		jwtIssuer:       jwtIssuer,
		accessTokenTTL:  accessTokenTTL,
		refreshTokenTTL: refreshTokenTTL,
	}
}

func (s *Service) Login(ctx context.Context, username, password, ipAddress, userAgent string) (LoginResponse, error) {
	record, err := s.repo.FindUserByUsername(ctx, username)
	if err != nil || !record.IsActive || !VerifyPassword(record.PasswordHash, password) {
		return LoginResponse{}, ErrInvalidCredentials
	}

	sessionID, err := s.repo.CreateSession(ctx, record.ID, time.Now().Add(s.refreshTokenTTL), ipAddress, userAgent)
	if err != nil {
		return LoginResponse{}, err
	}

	plainRefresh, hashedRefresh, err := NewRefreshToken()
	if err != nil {
		return LoginResponse{}, err
	}

	if err := s.repo.StoreRefreshToken(ctx, sessionID, record.ID, hashedRefresh, time.Now().Add(s.refreshTokenTTL)); err != nil {
		return LoginResponse{}, err
	}

	access, err := SignAccessToken(s.jwtSecret, s.jwtIssuer, s.accessTokenTTL, identity.Claims{
		UserID:   record.ID,
		Username: record.Username,
		Role:     record.Role,
		Session:  sessionID,
	})
	if err != nil {
		return LoginResponse{}, err
	}

	return LoginResponse{
		AccessToken:  access,
		RefreshToken: plainRefresh,
		ExpiresIn:    int64(s.accessTokenTTL.Seconds()),
		User: UserView{
			ID:          record.ID,
			Username:    record.Username,
			DisplayName: record.DisplayName,
			Role:        record.Role,
		},
	}, nil
}

func (s *Service) Refresh(ctx context.Context, refreshToken string) (LoginResponse, error) {
	record, sessionID, err := s.repo.FindRefreshToken(ctx, hashToken(refreshToken))
	if err != nil {
		return LoginResponse{}, ErrInvalidCredentials
	}

	access, err := SignAccessToken(s.jwtSecret, s.jwtIssuer, s.accessTokenTTL, identity.Claims{
		UserID:   record.ID,
		Username: record.Username,
		Role:     record.Role,
		Session:  sessionID,
	})
	if err != nil {
		return LoginResponse{}, err
	}

	return LoginResponse{
		AccessToken:  access,
		RefreshToken: refreshToken,
		ExpiresIn:    int64(s.accessTokenTTL.Seconds()),
		User: UserView{
			ID:          record.ID,
			Username:    record.Username,
			DisplayName: record.DisplayName,
			Role:        record.Role,
		},
	}, nil
}

func (s *Service) Logout(ctx context.Context, refreshToken string) error {
	return s.repo.RevokeRefreshToken(ctx, hashToken(refreshToken))
}

func (s *Service) BootstrapAvailable(ctx context.Context) (bool, error) {
	hasUsers, err := s.repo.HasUsers(ctx)
	if err != nil {
		return false, err
	}

	return !hasUsers, nil
}

func (s *Service) BootstrapAdmin(ctx context.Context, username, displayName, password string) (UserView, error) {
	hasUsers, err := s.repo.HasUsers(ctx)
	if err != nil {
		return UserView{}, err
	}
	if hasUsers {
		return UserView{}, errors.New("bootstrap already completed")
	}

	passwordHash, err := HashPassword(password)
	if err != nil {
		return UserView{}, err
	}

	record, err := s.repo.CreateBootstrapAdmin(ctx, username, displayName, passwordHash)
	if err != nil {
		return UserView{}, err
	}

	return UserView{
		ID:          record.ID,
		Username:    record.Username,
		DisplayName: record.DisplayName,
		Role:        record.Role,
	}, nil
}

func hashToken(plain string) string {
	return base64Hash(plain)
}
