package services

import (
	"context"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
	"time"

	"clawreef/internal/models"
	"clawreef/internal/repository"
)

const (
	ExternalAccessModeShareLink = "share_link"
	ExternalAccessModePassword  = "password"

	ExternalAccessExpirationPreset    = "preset"
	ExternalAccessExpirationCustom    = "custom"
	ExternalAccessExpirationPermanent = "permanent"

	ExternalAccessPreset1Hour   = "1h"
	ExternalAccessPreset24Hours = "24h"
	ExternalAccessPreset7Days   = "7d"
	ExternalAccessPreset30Days  = "30d"
)

type ExternalAccessExpirationRequest struct {
	Mode      string     `json:"expires_mode,omitempty"`
	Preset    string     `json:"expires_preset,omitempty"`
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
}

type EnableShareLinkResult struct {
	Access   *models.InstanceExternalAccess `json:"access"`
	ShareURL string                         `json:"share_url,omitempty"`
}

type PasswordExternalAccessResult struct {
	Access   *models.InstanceExternalAccess `json:"access"`
	Password string                         `json:"password"`
	ShareURL string                         `json:"share_url,omitempty"`
}

type InstanceExternalAccessService interface {
	Get(ctx context.Context, instanceID int) (*models.InstanceExternalAccess, error)
	EnableShareLink(ctx context.Context, instanceID, createdBy int, expiration ExternalAccessExpirationRequest) (*EnableShareLinkResult, error)
	CreatePassword(ctx context.Context, instanceID, createdBy int, expiration ExternalAccessExpirationRequest) (*PasswordExternalAccessResult, error)
	Disable(ctx context.Context, instanceID int) error
	ResolveShortLink(ctx context.Context, code string) (*models.InstanceExternalAccess, error)
	ValidateShortLink(ctx context.Context, code, password string) (*models.InstanceExternalAccess, error)
}

type instanceExternalAccessService struct {
	repo repository.InstanceExternalAccessRepository
}

func NewInstanceExternalAccessService(repo repository.InstanceExternalAccessRepository) InstanceExternalAccessService {
	return &instanceExternalAccessService{repo: repo}
}

func (s *instanceExternalAccessService) Get(ctx context.Context, instanceID int) (*models.InstanceExternalAccess, error) {
	if s == nil || s.repo == nil {
		return nil, fmt.Errorf("instance external access repository is not configured")
	}
	return s.repo.GetByInstanceID(ctx, instanceID)
}

func (s *instanceExternalAccessService) EnableShareLink(ctx context.Context, instanceID, createdBy int, expiration ExternalAccessExpirationRequest) (*EnableShareLinkResult, error) {
	if s == nil || s.repo == nil {
		return nil, fmt.Errorf("instance external access repository is not configured")
	}
	code, codeHash, err := generateShortCode()
	if err != nil {
		return nil, err
	}
	expiresAt, err := resolveExternalAccessExpiresAt(expiration)
	if err != nil {
		return nil, err
	}
	access := &models.InstanceExternalAccess{
		InstanceID:      instanceID,
		Enabled:         true,
		AuthMode:        ExternalAccessModeShareLink,
		ShortCodeHash:   &codeHash,
		PublicSlug:      &code,
		PublicTokenHash: nil,
		ExpiresAt:       expiresAt,
		CreatedBy:       &createdBy,
		PasswordHash:    nil,
		PasswordValue:   nil,
		PasswordHint:    nil,
		LastUsedAt:      nil,
	}
	if err := s.repo.Upsert(ctx, access); err != nil {
		return nil, err
	}
	saved, err := s.repo.GetByInstanceID(ctx, instanceID)
	if err != nil {
		return nil, err
	}
	return &EnableShareLinkResult{
		Access:   saved,
		ShareURL: shortExternalAccessURL(code),
	}, nil
}

func (s *instanceExternalAccessService) CreatePassword(ctx context.Context, instanceID, createdBy int, expiration ExternalAccessExpirationRequest) (*PasswordExternalAccessResult, error) {
	if s == nil || s.repo == nil {
		return nil, fmt.Errorf("instance external access repository is not configured")
	}
	code, codeHash, err := generateShortCode()
	if err != nil {
		return nil, err
	}
	password, err := randomToken("pwd", 16)
	if err != nil {
		return nil, err
	}
	expiresAt, err := resolveExternalAccessExpiresAt(expiration)
	if err != nil {
		return nil, err
	}
	passwordHash := hashExternalSecret(password)
	hint := password
	if len(hint) > 12 {
		hint = hint[:12]
	}
	access := &models.InstanceExternalAccess{
		InstanceID:      instanceID,
		Enabled:         true,
		AuthMode:        ExternalAccessModePassword,
		PublicSlug:      &code,
		ShortCodeHash:   &codeHash,
		PasswordHash:    &passwordHash,
		PasswordValue:   &password,
		PasswordHint:    &hint,
		ExpiresAt:       expiresAt,
		CreatedBy:       &createdBy,
		PublicTokenHash: nil,
		LastUsedAt:      nil,
	}
	if err := s.repo.Upsert(ctx, access); err != nil {
		return nil, err
	}
	saved, err := s.repo.GetByInstanceID(ctx, instanceID)
	if err != nil {
		return nil, err
	}
	return &PasswordExternalAccessResult{Access: saved, Password: password, ShareURL: shortExternalAccessURL(code)}, nil
}

func (s *instanceExternalAccessService) Disable(ctx context.Context, instanceID int) error {
	if s == nil || s.repo == nil {
		return fmt.Errorf("instance external access repository is not configured")
	}
	return s.repo.Disable(ctx, instanceID)
}

func (s *instanceExternalAccessService) ResolveShortLink(ctx context.Context, code string) (*models.InstanceExternalAccess, error) {
	if s == nil || s.repo == nil {
		return nil, fmt.Errorf("instance external access repository is not configured")
	}
	code = strings.TrimSpace(code)
	if code == "" {
		return nil, fmt.Errorf("short link code is required")
	}
	access, err := s.repo.GetByShortCodeHash(ctx, hashExternalSecret(code))
	if err != nil {
		return nil, err
	}
	if access == nil || !access.Enabled {
		return nil, fmt.Errorf("external access is not enabled")
	}
	if access.ExpiresAt != nil && time.Now().UTC().After(*access.ExpiresAt) {
		return nil, fmt.Errorf("external access has expired")
	}
	return access, nil
}

func (s *instanceExternalAccessService) ValidateShortLink(ctx context.Context, code, password string) (*models.InstanceExternalAccess, error) {
	access, err := s.ResolveShortLink(ctx, code)
	if err != nil {
		return nil, err
	}
	switch access.AuthMode {
	case ExternalAccessModeShareLink:
		// The short code itself is the share-link capability. A matching hash is enough.
	case ExternalAccessModePassword:
		if access.PasswordHash == nil || *access.PasswordHash != hashExternalSecret(strings.TrimSpace(password)) {
			return nil, fmt.Errorf("invalid external access password")
		}
	default:
		return nil, fmt.Errorf("unsupported external access mode")
	}
	if err := s.repo.MarkUsed(ctx, access.ID); err != nil {
		return nil, err
	}
	return access, nil
}

func resolveExternalAccessExpiresAt(expiration ExternalAccessExpirationRequest) (*time.Time, error) {
	mode := strings.TrimSpace(expiration.Mode)
	if mode == "" {
		mode = ExternalAccessExpirationPreset
	}
	switch mode {
	case ExternalAccessExpirationPermanent:
		return nil, nil
	case ExternalAccessExpirationCustom:
		if expiration.ExpiresAt == nil {
			return nil, fmt.Errorf("custom external access expiration requires expires_at")
		}
		value := expiration.ExpiresAt.UTC()
		return &value, nil
	case ExternalAccessExpirationPreset:
		preset := strings.TrimSpace(expiration.Preset)
		if preset == "" {
			preset = ExternalAccessPreset24Hours
		}
		duration, err := externalAccessPresetDuration(preset)
		if err != nil {
			return nil, err
		}
		value := time.Now().UTC().Add(duration)
		return &value, nil
	default:
		return nil, fmt.Errorf("unsupported external access expiration mode")
	}
}

func externalAccessPresetDuration(preset string) (time.Duration, error) {
	switch strings.TrimSpace(preset) {
	case ExternalAccessPreset1Hour:
		return time.Hour, nil
	case ExternalAccessPreset24Hours:
		return 24 * time.Hour, nil
	case ExternalAccessPreset7Days:
		return 7 * 24 * time.Hour, nil
	case ExternalAccessPreset30Days:
		return 30 * 24 * time.Hour, nil
	default:
		return 0, fmt.Errorf("unsupported external access expiration preset")
	}
}

func generateShortCode() (code, codeHash string, err error) {
	code, err = randomToken("sl", 12)
	if err != nil {
		return "", "", err
	}
	return code, hashExternalSecret(code), nil
}

func shortExternalAccessURL(code string) string {
	return fmt.Sprintf("/s/%s/", strings.Trim(strings.TrimSpace(code), "/"))
}

func ExternalAccessShareURL(access *models.InstanceExternalAccess) string {
	if access == nil || !access.Enabled || access.PublicSlug == nil {
		return ""
	}
	return shortExternalAccessURL(*access.PublicSlug)
}

func ExternalAccessPassword(access *models.InstanceExternalAccess) string {
	if access == nil || !access.Enabled || access.AuthMode != ExternalAccessModePassword || access.PasswordValue == nil {
		return ""
	}
	return *access.PasswordValue
}

func randomToken(prefix string, bytesLen int) (string, error) {
	buf := make([]byte, bytesLen)
	if _, err := rand.Read(buf); err != nil {
		return "", fmt.Errorf("failed to generate token: %w", err)
	}
	return prefix + "_" + hex.EncodeToString(buf), nil
}

func hashExternalSecret(value string) string {
	sum := sha256.Sum256([]byte(strings.TrimSpace(value)))
	return hex.EncodeToString(sum[:])
}
