// Package mospkg provides functionality for Mosis app packages (.mosis files)
package mospkg

import (
	"archive/zip"
	"fmt"
	"io"
	"path/filepath"
	"strings"
)

// Size limits
const (
	MaxPackageSize   = 50 * 1024 * 1024  // 50 MB
	MaxFileSize      = 10 * 1024 * 1024  // 10 MB
	MaxFileCount     = 1000
	MaxPathLength    = 256
	MaxManifestSize  = 64 * 1024         // 64 KB
)

// ValidationError represents a validation error
type ValidationError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	File    string `json:"file,omitempty"`
	Line    int    `json:"line,omitempty"`
}

// ValidationWarning represents a non-blocking validation warning
type ValidationWarning struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	File    string `json:"file,omitempty"`
}

// ValidationResult holds the result of package validation
type ValidationResult struct {
	Valid    bool                `json:"valid"`
	Errors   []ValidationError   `json:"errors,omitempty"`
	Warnings []ValidationWarning `json:"warnings,omitempty"`
	Manifest *Manifest           `json:"manifest,omitempty"`
}

// AllowedExtensions are file extensions permitted in packages
var AllowedExtensions = map[string]bool{
	".rml":  true,
	".rcss": true,
	".lua":  true,
	".png":  true,
	".jpg":  true,
	".jpeg": true,
	".tga":  true,
	".webp": true,
	".ttf":  true,
	".otf":  true,
	".json": true,
	".ogg":  true,
	".wav":  true,
	".mp3":  true,
}

// ForbiddenExtensions are file extensions not allowed in packages
var ForbiddenExtensions = map[string]bool{
	".exe":  true,
	".dll":  true,
	".so":   true,
	".dylib": true,
	".sh":   true,
	".bat":  true,
	".ps1":  true,
	".py":   true,
	".js":   true,
	".zip":  true,
	".tar":  true,
	".gz":   true,
}

// ValidatePackage validates a .mosis package file
func ValidatePackage(path string) (*ValidationResult, error) {
	result := &ValidationResult{Valid: true}

	// Open ZIP archive
	reader, err := zip.OpenReader(path)
	if err != nil {
		result.Valid = false
		result.Errors = append(result.Errors, ValidationError{
			Code:    "INVALID_ZIP",
			Message: fmt.Sprintf("Failed to open package: %v", err),
		})
		return result, nil
	}
	defer reader.Close()

	// Check file count
	if len(reader.File) > MaxFileCount {
		result.Valid = false
		result.Errors = append(result.Errors, ValidationError{
			Code:    "TOO_MANY_FILES",
			Message: fmt.Sprintf("Package contains %d files, maximum is %d", len(reader.File), MaxFileCount),
		})
		return result, nil
	}

	var manifestData []byte
	seenFiles := make(map[string]bool)
	var totalSize int64

	for _, file := range reader.File {
		name := file.Name

		// Check for path traversal
		if strings.Contains(name, "..") {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "PATH_TRAVERSAL",
				Message: "Path traversal detected",
				File:    name,
			})
			continue
		}

		// Check for absolute paths
		if filepath.IsAbs(name) {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "ABSOLUTE_PATH",
				Message: "Absolute path not allowed",
				File:    name,
			})
			continue
		}

		// Check path length
		if len(name) > MaxPathLength {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "PATH_TOO_LONG",
				Message: fmt.Sprintf("Path exceeds %d characters", MaxPathLength),
				File:    name,
			})
			continue
		}

		// Check for duplicates
		normalizedName := strings.ToLower(name)
		if seenFiles[normalizedName] {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "DUPLICATE_FILE",
				Message: "Duplicate file detected",
				File:    name,
			})
			continue
		}
		seenFiles[normalizedName] = true

		// Skip directories
		if file.FileInfo().IsDir() {
			continue
		}

		// Check individual file size
		if file.UncompressedSize64 > uint64(MaxFileSize) {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "FILE_TOO_LARGE",
				Message: fmt.Sprintf("File exceeds %d MB limit", MaxFileSize/(1024*1024)),
				File:    name,
			})
			continue
		}

		totalSize += int64(file.UncompressedSize64)

		// Check file extension
		ext := strings.ToLower(filepath.Ext(name))
		if ForbiddenExtensions[ext] {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "FORBIDDEN_EXTENSION",
				Message: fmt.Sprintf("File type %s is not allowed", ext),
				File:    name,
			})
			continue
		}

		// Skip META-INF files for extension check
		if !strings.HasPrefix(name, "META-INF/") && ext != "" && !AllowedExtensions[ext] {
			result.Warnings = append(result.Warnings, ValidationWarning{
				Code:    "UNKNOWN_EXTENSION",
				Message: fmt.Sprintf("Unknown file extension: %s", ext),
				File:    name,
			})
		}

		// Read manifest.json
		if name == "manifest.json" {
			rc, err := file.Open()
			if err != nil {
				result.Valid = false
				result.Errors = append(result.Errors, ValidationError{
					Code:    "MANIFEST_READ_ERROR",
					Message: fmt.Sprintf("Failed to read manifest: %v", err),
				})
				continue
			}
			manifestData, err = io.ReadAll(io.LimitReader(rc, MaxManifestSize))
			rc.Close()
			if err != nil {
				result.Valid = false
				result.Errors = append(result.Errors, ValidationError{
					Code:    "MANIFEST_READ_ERROR",
					Message: fmt.Sprintf("Failed to read manifest: %v", err),
				})
			}
		}
	}

	// Check total size
	if totalSize > MaxPackageSize {
		result.Valid = false
		result.Errors = append(result.Errors, ValidationError{
			Code:    "PACKAGE_TOO_LARGE",
			Message: fmt.Sprintf("Package exceeds %d MB limit", MaxPackageSize/(1024*1024)),
		})
	}

	// Validate manifest
	if manifestData == nil {
		result.Valid = false
		result.Errors = append(result.Errors, ValidationError{
			Code:    "MISSING_MANIFEST",
			Message: "Package is missing manifest.json",
		})
	} else {
		manifest, err := ParseManifest(manifestData)
		if err != nil {
			result.Valid = false
			result.Errors = append(result.Errors, ValidationError{
				Code:    "INVALID_MANIFEST",
				Message: fmt.Sprintf("Invalid manifest.json: %v", err),
			})
		} else {
			result.Manifest = manifest
			manifestErrors := manifest.Validate()
			if len(manifestErrors) > 0 {
				result.Valid = false
				result.Errors = append(result.Errors, manifestErrors...)
			}

			// Check that entry point exists
			entryExists := false
			for _, file := range reader.File {
				if file.Name == manifest.Entry {
					entryExists = true
					break
				}
			}
			if !entryExists {
				result.Valid = false
				result.Errors = append(result.Errors, ValidationError{
					Code:    "MISSING_ENTRY",
					Message: fmt.Sprintf("Entry point file not found: %s", manifest.Entry),
				})
			}

			// Check icons
			if result.Manifest.Icons.Size32 != "" {
				if !fileExistsInZip(reader, result.Manifest.Icons.Size32) {
					result.Errors = append(result.Errors, ValidationError{
						Code:    "MISSING_ICON",
						Message: "Icon file not found",
						File:    result.Manifest.Icons.Size32,
					})
				}
			}
			if result.Manifest.Icons.Size64 != "" {
				if !fileExistsInZip(reader, result.Manifest.Icons.Size64) {
					result.Errors = append(result.Errors, ValidationError{
						Code:    "MISSING_ICON",
						Message: "Icon file not found",
						File:    result.Manifest.Icons.Size64,
					})
				}
			}
			if result.Manifest.Icons.Size128 != "" {
				if !fileExistsInZip(reader, result.Manifest.Icons.Size128) {
					result.Errors = append(result.Errors, ValidationError{
						Code:    "MISSING_ICON",
						Message: "Icon file not found",
						File:    result.Manifest.Icons.Size128,
					})
				}
			}
		}
	}

	return result, nil
}

func fileExistsInZip(reader *zip.ReadCloser, name string) bool {
	for _, file := range reader.File {
		if file.Name == name {
			return true
		}
	}
	return false
}
