package core

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"image/png"
	"io"
	"mime/multipart"
	"os"
	"strings"
	"time"

	"gitea.mrixs.me/minecraft-platform/backend/internal/database"
	"gitea.mrixs.me/minecraft-platform/backend/internal/models"
	"github.com/google/uuid"
)

type ProfileService struct {
	UserRepo   *database.UserRepository
	privateKey *rsa.PrivateKey
	domain     string
}

// NewProfileService создает новый сервис и загружает приватный ключ.
func NewProfileService(repo *database.UserRepository, keyPath, domain string) (*ProfileService, error) {
	keyData, err := os.ReadFile(keyPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read private key file: %w", err)
	}

	block, _ := pem.Decode(keyData)
	if block == nil {
		return nil, errors.New("failed to parse PEM block containing the key")
	}

	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		key, errPkcs8 := x509.ParsePKCS8PrivateKey(block.Bytes)
		if errPkcs8 != nil {
			return nil, fmt.Errorf("failed to parse private key: %v / %v", err, errPkcs8)
		}
		var ok bool
		privateKey, ok = key.(*rsa.PrivateKey)
		if !ok {
			return nil, errors.New("key is not an RSA private key")
		}
	}

	return &ProfileService{
		UserRepo:   repo,
		privateKey: privateKey,
		domain:     domain,
	}, nil
}

// GetSignedProfile формирует и подписывает профиль игрока.
func (s *ProfileService) GetSignedProfile(ctx context.Context, playerUUID uuid.UUID) (*models.SessionProfileResponse, error) {
	user, profile, err := s.UserRepo.GetProfileByUUID(ctx, playerUUID)
	if err != nil {
		return nil, err
	}

	textures := models.Textures{}
	if profile.SkinHash != "" {
		textures.SKIN = &models.TextureInfo{URL: fmt.Sprintf("http://%s/files/textures/%s", s.domain, profile.SkinHash)}
	}
	if profile.CapeHash != "" {
		textures.CAPE = &models.TextureInfo{URL: fmt.Sprintf("http://%s/files/textures/%s", s.domain, profile.CapeHash)}
	}

	profileID := strings.ReplaceAll(user.UUID.String(), "-", "")
	propValue := models.ProfilePropertyValue{
		Timestamp:   time.Now().UnixMilli(),
		ProfileID:   profileID,
		ProfileName: user.Username,
		Textures:    textures,
	}

	valueJSON, err := json.Marshal(propValue)
	if err != nil {
		return nil, err
	}
	valueBase64 := base64.StdEncoding.EncodeToString(valueJSON)

	hasher := sha1.New()
	hasher.Write([]byte(valueBase64))
	hashed := hasher.Sum(nil)

	signature, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, crypto.SHA1, hashed)
	if err != nil {
		return nil, err
	}
	signatureBase64 := base64.StdEncoding.EncodeToString(signature)

	response := &models.SessionProfileResponse{
		ID:   profileID,
		Name: user.Username,
		Properties: []models.ProfileProperty{
			{
				Name:      "textures",
				Value:     valueBase64,
				Signature: signatureBase64,
			},
		},
	}

	return response, nil
}

// UpdateUserSkin обрабатывает загрузку, валидацию и сохранение файла скина.
func (s *ProfileService) UpdateUserSkin(ctx context.Context, userID int, file multipart.File, header *multipart.FileHeader) error {
	fileBytes, err := io.ReadAll(file)
	if err != nil {
		return fmt.Errorf("failed to read file: %w", err)
	}
	file.Seek(0, 0)

	config, err := png.DecodeConfig(file)
	if err != nil {
		return errors.New("invalid PNG file")
	}
	if config.Width != 64 || config.Height != 64 {
		return errors.New("skin must be 64x64 pixels")
	}

	hasher := sha1.New()
	hasher.Write(fileBytes)
	hash := hex.EncodeToString(hasher.Sum(nil))

	storagePath := os.Getenv("TEXTURES_STORAGE_PATH")
	if storagePath == "" {
		return errors.New("textures storage path not configured")
	}
	filePath := fmt.Sprintf("%s/%s", storagePath, hash)

	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		outFile, err := os.Create(filePath)
		if err != nil {
			return fmt.Errorf("failed to create skin file: %w", err)
		}
		defer outFile.Close()

		_, err = outFile.Write(fileBytes)
		if err != nil {
			return fmt.Errorf("failed to write skin file: %w", err)
		}
	}

	return s.UserRepo.UpdateSkinHash(ctx, userID, hash)
}

// GetPublicKey возвращает публичный ключ в формате PEM
func (s *ProfileService) GetPublicKey() (string, error) {
	pubKey := &s.privateKey.PublicKey
	pubASN1, err := x509.MarshalPKIXPublicKey(pubKey)
	if err != nil {
		return "", err
	}

	pubBytes := pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: pubASN1,
	})

	return string(pubBytes), nil
}
