// Package review provides app review and validation services
package review

import (
	"archive/zip"
	"context"
	"fmt"
	"io"
	"regexp"
	"strings"
	"time"

	"omixlab.com/mosis-portal/internal/database"
	"omixlab.com/mosis-portal/pkg/mospkg"
)

// ReviewFlag represents a security or quality flag
type ReviewFlag struct {
	Type     string `json:"type"`
	Severity string `json:"severity"` // "info", "warning", "critical"
	Reason   string `json:"reason"`
	File     string `json:"file,omitempty"`
	Line     int    `json:"line,omitempty"`
}

// FullValidationResult extends ValidationResult with security flags
type FullValidationResult struct {
	*mospkg.ValidationResult
	Flags            []ReviewFlag `json:"flags,omitempty"`
	RequiresManual   bool         `json:"requires_manual"`
	AutoApprovable   bool         `json:"auto_approvable"`
	ValidationTimeMs int64        `json:"validation_time_ms"`
}

// Service handles app review operations
type Service struct {
	db *database.DB
}

// New creates a new review service
func New(db *database.DB) *Service {
	return &Service{db: db}
}

// ValidatePackage performs full validation on a package
func (s *Service) ValidatePackage(packagePath string) (*FullValidationResult, error) {
	start := time.Now()

	// Run basic validation
	basicResult, err := mospkg.ValidatePackage(packagePath)
	if err != nil {
		return nil, err
	}

	result := &FullValidationResult{
		ValidationResult: basicResult,
		Flags:            []ReviewFlag{},
		AutoApprovable:   true,
	}

	// If basic validation failed, no need to continue
	if !basicResult.Valid {
		result.AutoApprovable = false
		result.ValidationTimeMs = time.Since(start).Milliseconds()
		return result, nil
	}

	// Run security analysis (Tier 3)
	flags, err := s.analyzeSecurityRisks(packagePath)
	if err != nil {
		return nil, err
	}
	result.Flags = append(result.Flags, flags...)

	// Run quality checks (Tier 4)
	qualityFlags := s.checkQuality(basicResult.Manifest)
	result.Flags = append(result.Flags, qualityFlags...)

	// Determine if manual review is required
	result.RequiresManual = s.requiresManualReview(result)
	if result.RequiresManual {
		result.AutoApprovable = false
	}

	// Check for critical flags
	for _, flag := range result.Flags {
		if flag.Severity == "critical" {
			result.AutoApprovable = false
		}
	}

	result.ValidationTimeMs = time.Since(start).Milliseconds()
	return result, nil
}

// SubmitForReview submits a version for review
func (s *Service) SubmitForReview(ctx context.Context, versionID string) error {
	return s.db.UpdateVersionStatus(ctx, versionID, "in_review")
}

// ApproveVersion approves a version
func (s *Service) ApproveVersion(ctx context.Context, versionID, reviewerNotes string) error {
	return s.db.ApproveVersion(ctx, versionID, reviewerNotes)
}

// RejectVersion rejects a version with feedback
func (s *Service) RejectVersion(ctx context.Context, versionID string, feedback *RejectionFeedback) error {
	return s.db.RejectVersion(ctx, versionID, feedback.Reason, feedback.Message)
}

// RejectionFeedback contains rejection details
type RejectionFeedback struct {
	Reason      string           `json:"reason"`
	Message     string           `json:"message"`
	Details     []RejectionDetail `json:"details,omitempty"`
	CanResubmit bool             `json:"can_resubmit"`
}

// RejectionDetail provides specific feedback for an issue
type RejectionDetail struct {
	File       string `json:"file"`
	Line       int    `json:"line,omitempty"`
	Issue      string `json:"issue"`
	Suggestion string `json:"suggestion,omitempty"`
}

// Dangerous patterns to detect in Lua code
var dangerousPatterns = []struct {
	Pattern  *regexp.Regexp
	Reason   string
	Severity string
}{
	{
		regexp.MustCompile(`loadstring\s*\(`),
		"Dynamic code execution via loadstring",
		"critical",
	},
	{
		regexp.MustCompile(`load\s*\([^)]*\)`),
		"Dynamic code loading",
		"critical",
	},
	{
		regexp.MustCompile(`debug\s*\.\s*\w+`),
		"Debug library usage",
		"critical",
	},
	{
		regexp.MustCompile(`os\s*\.\s*execute`),
		"OS command execution",
		"critical",
	},
	{
		regexp.MustCompile(`os\s*\.\s*remove`),
		"File deletion via os.remove",
		"critical",
	},
	{
		regexp.MustCompile(`io\s*\.\s*(open|popen|read|write|lines)`),
		"Direct file I/O operations",
		"critical",
	},
	{
		regexp.MustCompile(`ffi\s*\.\s*\w+`),
		"FFI (foreign function interface) usage",
		"critical",
	},
	{
		regexp.MustCompile(`package\s*\.\s*loadlib`),
		"Native library loading",
		"critical",
	},
	{
		regexp.MustCompile(`package\s*\.\s*cpath`),
		"C library path modification",
		"critical",
	},
	{
		regexp.MustCompile(`rawset\s*\(\s*_G`),
		"Global environment modification",
		"warning",
	},
	{
		regexp.MustCompile(`setfenv\s*\(`),
		"Environment modification",
		"warning",
	},
	{
		regexp.MustCompile(`getfenv\s*\(`),
		"Environment access",
		"warning",
	},
	{
		regexp.MustCompile(`https?://[^\s"']+`),
		"Hardcoded external URL",
		"info",
	},
	{
		regexp.MustCompile(`require\s*\(\s*["'][^"']+["']\s*\)`),
		"Module require (verify allowed modules)",
		"info",
	},
}

func (s *Service) analyzeSecurityRisks(packagePath string) ([]ReviewFlag, error) {
	var flags []ReviewFlag

	reader, err := zip.OpenReader(packagePath)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	for _, file := range reader.File {
		if file.FileInfo().IsDir() {
			continue
		}

		ext := strings.ToLower(strings.TrimPrefix(file.Name, "."))
		if !strings.HasSuffix(file.Name, ".lua") {
			continue
		}

		// Read Lua file content
		rc, err := file.Open()
		if err != nil {
			continue
		}

		content, err := io.ReadAll(io.LimitReader(rc, 1024*1024)) // 1MB limit
		rc.Close()
		if err != nil {
			continue
		}

		contentStr := string(content)

		// Check against dangerous patterns
		for _, dp := range dangerousPatterns {
			if dp.Pattern.MatchString(contentStr) {
				// Find line number
				lineNum := findLineNumber(contentStr, dp.Pattern)

				flags = append(flags, ReviewFlag{
					Type:     "SECURITY",
					Severity: dp.Severity,
					Reason:   dp.Reason,
					File:     file.Name,
					Line:     lineNum,
				})
			}
		}

		// Check for obfuscated code (high entropy, meaningless variable names)
		if isLikelyObfuscated(contentStr) {
			flags = append(flags, ReviewFlag{
				Type:     "SECURITY",
				Severity: "warning",
				Reason:   "Code appears to be obfuscated",
				File:     file.Name,
			})
		}

		_ = ext // Unused but may be useful for future file type checks
	}

	return flags, nil
}

func findLineNumber(content string, pattern *regexp.Regexp) int {
	loc := pattern.FindStringIndex(content)
	if loc == nil {
		return 0
	}

	lineNum := 1
	for i := 0; i < loc[0] && i < len(content); i++ {
		if content[i] == '\n' {
			lineNum++
		}
	}
	return lineNum
}

func isLikelyObfuscated(content string) bool {
	// Simple heuristics for obfuscation detection:
	// 1. High ratio of single-character variable names
	// 2. Many string.char() calls
	// 3. Long lines with minimal whitespace

	singleCharVars := regexp.MustCompile(`\blocal\s+[a-z]\s*=`)
	matches := singleCharVars.FindAllString(content, -1)
	if len(matches) > 20 {
		return true
	}

	stringCharCalls := strings.Count(content, "string.char")
	if stringCharCalls > 10 {
		return true
	}

	// Check for long lines (obfuscators often produce very long lines)
	lines := strings.Split(content, "\n")
	longLines := 0
	for _, line := range lines {
		if len(line) > 500 {
			longLines++
		}
	}
	if longLines > 3 {
		return true
	}

	return false
}

func (s *Service) checkQuality(manifest *mospkg.Manifest) []ReviewFlag {
	var flags []ReviewFlag

	if manifest == nil {
		return flags
	}

	// Check description length
	if len(manifest.Description) < 10 {
		flags = append(flags, ReviewFlag{
			Type:     "QUALITY",
			Severity: "info",
			Reason:   "Description is very short (less than 10 characters)",
		})
	}

	// Check for placeholder icon paths
	if manifest.Icons.Size32 == "" && manifest.Icons.Size64 == "" && manifest.Icons.Size128 == "" {
		flags = append(flags, ReviewFlag{
			Type:     "QUALITY",
			Severity: "warning",
			Reason:   "No icons specified in manifest",
		})
	}

	// Check for sensitive permissions
	sensitivePerms := map[string]bool{
		"camera":     true,
		"microphone": true,
		"contacts":   true,
		"location":   true,
	}
	for _, perm := range manifest.Permissions {
		if sensitivePerms[perm] {
			flags = append(flags, ReviewFlag{
				Type:     "PERMISSION",
				Severity: "info",
				Reason:   fmt.Sprintf("App requests sensitive permission: %s", perm),
			})
		}
	}

	return flags
}

func (s *Service) requiresManualReview(result *FullValidationResult) bool {
	// Always require manual review for:
	// 1. Any critical security flags
	// 2. More than 3 warnings
	// 3. Sensitive permissions

	criticalCount := 0
	warningCount := 0
	hasSensitivePerms := false

	for _, flag := range result.Flags {
		switch flag.Severity {
		case "critical":
			criticalCount++
		case "warning":
			warningCount++
		}
		if flag.Type == "PERMISSION" {
			hasSensitivePerms = true
		}
	}

	if criticalCount > 0 {
		return true
	}
	if warningCount > 3 {
		return true
	}
	if hasSensitivePerms {
		return true
	}

	return false
}

// GetReviewQueue returns versions pending review
func (s *Service) GetReviewQueue(ctx context.Context, limit, offset int) ([]database.VersionWithApp, int, error) {
	return s.db.GetVersionsInReview(ctx, limit, offset)
}

// GetReviewDetails returns details for a specific version under review
func (s *Service) GetReviewDetails(ctx context.Context, versionID string) (*database.VersionWithApp, error) {
	return s.db.GetVersionWithApp(ctx, versionID)
}
