// Copyright 2026 Cisco Systems, Inc. and its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

package scanner

import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"math"
	"os"
	"regexp"
	"time"
)

// ClawShieldMalwareScanner detects malicious content via magic bytes, entropy, and signatures.
// Ported from github.com/Jason-Cyr/OpenClawSecurity — all logic runs natively in-process.
type ClawShieldMalwareScanner struct {
	MaxEntropy float64
}

func NewClawShieldMalwareScanner() *ClawShieldMalwareScanner {
	return &ClawShieldMalwareScanner{MaxEntropy: 7.0}
}

func (s *ClawShieldMalwareScanner) Name() string               { return "clawshield-malware" }
func (s *ClawShieldMalwareScanner) Version() string            { return "1.0.0" }
func (s *ClawShieldMalwareScanner) SupportedTargets() []string { return []string{"skill", "code"} }

type csMagicSig struct {
	name     string
	magic    []byte
	severity Severity
}

var csMagicSigs = []csMagicSig{
	{name: "ELF executable", magic: []byte{0x7f, 'E', 'L', 'F'}, severity: SeverityCritical},
	{name: "PE/Windows executable", magic: []byte{'M', 'Z'}, severity: SeverityCritical},
	{name: "Mach-O (32-bit)", magic: []byte{0xfe, 0xed, 0xfa, 0xce}, severity: SeverityCritical},
	{name: "Mach-O (64-bit)", magic: []byte{0xfe, 0xed, 0xfa, 0xcf}, severity: SeverityCritical},
	{name: "Mach-O (32-bit LE)", magic: []byte{0xce, 0xfa, 0xed, 0xfe}, severity: SeverityCritical},
	{name: "Mach-O (64-bit LE)", magic: []byte{0xcf, 0xfa, 0xed, 0xfe}, severity: SeverityCritical},
	{name: "Java class file", magic: []byte{0xca, 0xfe, 0xba, 0xbe}, severity: SeverityHigh},
	{name: "WebAssembly", magic: []byte{0x00, 'a', 's', 'm'}, severity: SeverityHigh},
}

var csScriptPrefixes = []struct{ name, prefix string }{
	{"bash script", "#!/bin/bash"},
	{"sh script", "#!/bin/sh"},
	{"env bash", "#!/usr/bin/env bash"},
	{"env sh", "#!/usr/bin/env sh"},
	{"python script", "#!/usr/bin/python"},
	{"env python", "#!/usr/bin/env python"},
	{"perl script", "#!/usr/bin/perl"},
	{"env perl", "#!/usr/bin/env perl"},
	{"ruby script", "#!/usr/bin/ruby"},
	{"env ruby", "#!/usr/bin/env ruby"},
	{"node script", "#!/usr/bin/env node"},
}

type csSigPattern struct {
	id          string
	category    string
	pattern     *regexp.Regexp
	severity    Severity
	remediation string
}

var csSigPatterns = []csSigPattern{
	// Reverse shells
	{id: "CS-MAL-RS-DEVTCP", category: "reverse_shell", pattern: regexp.MustCompile(`/dev/tcp/`), severity: SeverityCritical, remediation: "Remove reverse shell payload; investigate how it was introduced"},
	{id: "CS-MAL-RS-BASH", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)bash\s+-i\s+>&`), severity: SeverityCritical, remediation: "Remove reverse shell payload; investigate how it was introduced"},
	{id: "CS-MAL-RS-NC", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)\bnc\s+-[elp]`), severity: SeverityHigh, remediation: "Review netcat usage; ensure it is not used for unauthorized connections"},
	{id: "CS-MAL-RS-NCAT", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)\bncat\s+.*-[ec]`), severity: SeverityHigh, remediation: "Review ncat usage for potential reverse shell"},
	{id: "CS-MAL-RS-PYTHON", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)python[23]?\s+-c\s+.*socket`), severity: SeverityCritical, remediation: "Remove Python socket reverse shell payload"},
	{id: "CS-MAL-RS-RUBY", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)ruby\s+-rsocket`), severity: SeverityCritical, remediation: "Remove Ruby socket reverse shell payload"},
	{id: "CS-MAL-RS-PERL", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)perl\s+-e\s+.*socket`), severity: SeverityCritical, remediation: "Remove Perl socket reverse shell payload"},
	{id: "CS-MAL-RS-PHP", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)php\s+-r\s+.*fsockopen`), severity: SeverityCritical, remediation: "Remove PHP fsockopen reverse shell payload"},
	{id: "CS-MAL-RS-SOCAT", category: "reverse_shell", pattern: regexp.MustCompile(`(?i)\bsocat\s+.*exec`), severity: SeverityHigh, remediation: "Review socat exec usage for potential reverse shell"},

	// Credential harvesting
	{id: "CS-MAL-CRED-MIMIKATZ", category: "credential_harvest", pattern: regexp.MustCompile(`(?i)\bmimikatz\b`), severity: SeverityCritical, remediation: "Mimikatz is a credential dumping tool — remove immediately"},
	{id: "CS-MAL-CRED-SEKURLSA", category: "credential_harvest", pattern: regexp.MustCompile(`(?i)\bsekurlsa\b`), severity: SeverityCritical, remediation: "sekurlsa is a mimikatz module — remove immediately"},
	{id: "CS-MAL-CRED-LSADUMP", category: "credential_harvest", pattern: regexp.MustCompile(`(?i)\blsadump\b`), severity: SeverityCritical, remediation: "lsadump is used for credential extraction — remove immediately"},
	{id: "CS-MAL-CRED-HASHDUMP", category: "credential_harvest", pattern: regexp.MustCompile(`(?i)\bhashdump\b`), severity: SeverityCritical, remediation: "hashdump extracts password hashes — remove immediately"},
	{id: "CS-MAL-CRED-DUMP", category: "credential_harvest", pattern: regexp.MustCompile(`(?i)credential[s]?\s*dump`), severity: SeverityHigh, remediation: "Review credential dump reference"},

	// Cryptominers
	{id: "CS-MAL-MINER-STRATUM", category: "cryptominer", pattern: regexp.MustCompile(`stratum\+tcp://`), severity: SeverityCritical, remediation: "Cryptomining pool connection detected — remove payload"},
	{id: "CS-MAL-MINER-XMRIG", category: "cryptominer", pattern: regexp.MustCompile(`(?i)\bxmrig\b`), severity: SeverityCritical, remediation: "XMRig miner detected — remove payload"},
	{id: "CS-MAL-MINER-CRYPTONIGHT", category: "cryptominer", pattern: regexp.MustCompile(`(?i)\bcryptonight\b`), severity: SeverityHigh, remediation: "CryptoNight mining algorithm reference — investigate"},
	{id: "CS-MAL-MINER-COINHIVE", category: "cryptominer", pattern: regexp.MustCompile(`(?i)\bcoinhive\b`), severity: SeverityCritical, remediation: "Coinhive browser miner detected — remove"},
	{id: "CS-MAL-MINER-MINERGATE", category: "cryptominer", pattern: regexp.MustCompile(`(?i)\bminergate\b`), severity: SeverityHigh, remediation: "MinerGate reference — investigate for mining payload"},

	// C2 frameworks
	{id: "CS-MAL-C2-METERPRETER", category: "c2", pattern: regexp.MustCompile(`(?i)\bmeterpreter\b`), severity: SeverityCritical, remediation: "Meterpreter is a Metasploit C2 payload — remove immediately"},
	{id: "CS-MAL-C2-COBALT", category: "c2", pattern: regexp.MustCompile(`(?i)cobalt\s*strike`), severity: SeverityCritical, remediation: "Cobalt Strike beacon reference — remove immediately"},
	{id: "CS-MAL-C2-EMPIRE", category: "c2", pattern: regexp.MustCompile(`(?i)\bpowershell\s+empire\b`), severity: SeverityCritical, remediation: "PowerShell Empire C2 framework reference — remove immediately"},
	{id: "CS-MAL-C2-SLIVER", category: "c2", pattern: regexp.MustCompile(`(?i)\bsliver\s+(?:c2|implant|beacon)\b`), severity: SeverityHigh, remediation: "Sliver C2 framework reference — investigate"},
	{id: "CS-MAL-C2-BEACON", category: "c2", pattern: regexp.MustCompile(`(?i)\bbeacon\s*(?:\.exe|payload|stage|config)\b`), severity: SeverityHigh, remediation: "C2 beacon payload reference — investigate"},
}

func (s *ClawShieldMalwareScanner) Scan(ctx context.Context, target string) (*ScanResult, error) {
	start := time.Now()
	result := &ScanResult{Scanner: s.Name(), Target: target, Timestamp: start}

	maxEntropy := s.MaxEntropy
	if maxEntropy <= 0 {
		maxEntropy = 7.0
	}

	files, err := csCollectAllFiles(target)
	if err != nil {
		return nil, fmt.Errorf("scanner: clawshield-malware: %w", err)
	}

	for _, f := range files {
		content, err := os.ReadFile(f)
		if err != nil {
			continue
		}
		result.Findings = append(result.Findings, csMalwareScanContent(content, f, maxEntropy)...)
	}

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

func csMalwareScanContent(content []byte, path string, maxEntropy float64) []Finding {
	var findings []Finding

	// Magic byte detection.
	for _, sig := range csMagicSigs {
		if len(content) >= len(sig.magic) && bytes.Equal(content[:len(sig.magic)], sig.magic) {
			findings = append(findings, Finding{
				ID:          "CS-MAL-MAGIC-" + sig.name,
				Severity:    sig.severity,
				Title:       fmt.Sprintf("Executable format detected: %s", sig.name),
				Description: fmt.Sprintf("File starts with magic bytes for %s", sig.name),
				Location:    path,
				Remediation: "Verify this binary is intentional; executables in skill directories are suspicious",
				Scanner:     "clawshield-malware",
				Tags:        []string{"malware", "magic_bytes", "clawshield"},
			})
		}
	}

	// Script shebang detection.
	text := string(content)
	for _, sp := range csScriptPrefixes {
		if len(text) >= len(sp.prefix) && text[:len(sp.prefix)] == sp.prefix {
			findings = append(findings, Finding{
				ID:          "CS-MAL-SCRIPT",
				Severity:    SeverityMedium,
				Title:       fmt.Sprintf("Script detected: %s", sp.name),
				Description: fmt.Sprintf("File begins with shebang for %s", sp.name),
				Location:    path,
				Remediation: "Review script content for malicious commands before execution",
				Scanner:     "clawshield-malware",
				Tags:        []string{"malware", "script", "clawshield"},
			})
			break
		}
	}

	// Signature pattern matching.
	for _, sig := range csSigPatterns {
		if loc := sig.pattern.FindStringIndex(text); loc != nil {
			findings = append(findings, Finding{
				ID:          sig.id,
				Severity:    sig.severity,
				Title:       fmt.Sprintf("Malicious signature: %s (%s)", sig.id, sig.category),
				Description: fmt.Sprintf("Matched: %s", csTruncateMatch(text[loc[0]:loc[1]])),
				Location:    csLocation(path, content, loc[0]),
				Remediation: sig.remediation,
				Scanner:     "clawshield-malware",
				Tags:        []string{"malware", sig.category, "clawshield"},
			})
		}
	}

	// High-entropy base64 blob detection.
	b64Pat := regexp.MustCompile(`[A-Za-z0-9+/]{256,}={0,2}`)
	for _, match := range b64Pat.FindAll(content, 5) {
		decoded, err := base64.StdEncoding.DecodeString(string(match))
		if err != nil || len(decoded) < 256 {
			continue
		}
		entropy := csByteEntropy(decoded)
		if entropy > maxEntropy {
			findings = append(findings, Finding{
				ID:          "CS-MAL-HIGH-ENTROPY",
				Severity:    SeverityHigh,
				Title:       fmt.Sprintf("High entropy base64 blob: %.2f (threshold: %.1f)", entropy, maxEntropy),
				Description: "High entropy in a large base64 blob suggests encrypted or packed payload",
				Location:    path,
				Remediation: "Investigate base64 content; packed/encrypted data may indicate obfuscated malware",
				Scanner:     "clawshield-malware",
				Tags:        []string{"malware", "high_entropy", "clawshield"},
			})
		}
	}

	// Archive safety: zip slip and nested archives.
	isZip := len(content) >= 4 && content[0] == 'P' && content[1] == 'K' && content[2] == 0x03 && content[3] == 0x04
	if isZip {
		if bytes.Contains(content, []byte("../")) || bytes.Contains(content, []byte(`..\ `)) {
			findings = append(findings, Finding{
				ID:          "CS-MAL-ZIP-SLIP",
				Severity:    SeverityHigh,
				Title:       "Archive contains path traversal patterns (zip slip)",
				Description: "ZIP archive contains ../ entries that could extract outside the target directory",
				Location:    path,
				Remediation: "Validate all zip entry paths before extraction; reject entries with .. sequences",
				Scanner:     "clawshield-malware",
				Tags:        []string{"malware", "archive_traversal", "zip_slip", "clawshield"},
			})
		}
		if bytes.Count(content, []byte("PK\x03\x04")) > 1 {
			findings = append(findings, Finding{
				ID:          "CS-MAL-NESTED-ARCHIVE",
				Severity:    SeverityMedium,
				Title:       "Nested archive detected (potential zip bomb layer)",
				Description: "Archive contains another embedded archive, which may be a zip bomb",
				Location:    path,
				Remediation: "Set decompression size limits; refuse to extract archives beyond threshold",
				Scanner:     "clawshield-malware",
				Tags:        []string{"malware", "nested_archive", "zip_bomb", "clawshield"},
			})
		}
	}

	return findings
}

// csByteEntropy calculates Shannon entropy over raw bytes.
func csByteEntropy(data []byte) float64 {
	if len(data) == 0 {
		return 0
	}
	var freq [256]float64
	for _, b := range data {
		freq[b]++
	}
	total := float64(len(data))
	entropy := 0.0
	for _, count := range freq {
		if count > 0 {
			p := count / total
			entropy -= p * math.Log2(p)
		}
	}
	return entropy
}
