package service

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
)

const defaultTokenTTL = 24 * time.Hour

func hashPassword(password string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

func checkPassword(hash, password string) error {
	if strings.TrimSpace(hash) == "" {
		return errors.New("password hash is empty")
	}
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}

func tokenSecret() string {
	secret := strings.TrimSpace(os.Getenv("TOKEN_SECRET"))
	if secret == "" {
		return "local-dev-secret-change-me"
	}
	return secret
}

func tokenTTL() time.Duration {
	raw := strings.TrimSpace(os.Getenv("TOKEN_TTL"))
	if raw == "" {
		return defaultTokenTTL
	}
	ttl, err := time.ParseDuration(raw)
	if err != nil || ttl <= 0 {
		return defaultTokenTTL
	}
	return ttl
}

func signToken(payload tokenPayload) (string, error) {
	bytes, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	body := base64.RawURLEncoding.EncodeToString(bytes)
	signature := computeTokenSignature(body)
	return "fable-token." + body + "." + signature, nil
}

func parseToken(token string) (tokenPayload, error) {
	parts := strings.Split(token, ".")
	if len(parts) != 3 || parts[0] != "fable-token" {
		return tokenPayload{}, errors.New("invalid token format")
	}
	if !hmac.Equal([]byte(parts[2]), []byte(computeTokenSignature(parts[1]))) {
		return tokenPayload{}, errors.New("invalid token signature")
	}
	bytes, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return tokenPayload{}, err
	}
	var payload tokenPayload
	if err := json.Unmarshal(bytes, &payload); err != nil {
		return tokenPayload{}, err
	}
	if payload.ExpiresAt > 0 && time.Now().UTC().Unix() > payload.ExpiresAt {
		return tokenPayload{}, errors.New("token expired")
	}
	return payload, nil
}

func computeTokenSignature(body string) string {
	mac := hmac.New(sha256.New, []byte(tokenSecret()))
	_, _ = mac.Write([]byte(body))
	return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
