package com.optix.android.tools.cloud

import com.optix.android.domain.model.ParamType
import com.optix.android.domain.model.ToolCategory
import com.optix.android.domain.model.ToolMetadata
import com.optix.android.domain.model.ToolParam
import com.optix.android.domain.model.ToolEvent
import com.optix.android.tools.engine.ShellExecutor
import com.optix.android.tools.registry.BaseTool
import javax.inject.Inject

/**
 * GCP Asset Scanner - Cloud security reconnaissance for Google Cloud Platform.
 * 
 * Enumerates GCP resources, analyzes security configurations, and identifies
 * potential misconfigurations across Compute Engine, Cloud Storage, IAM, and firewalls.
 * 
 * ## Features
 * - Compute Engine instance enumeration with network analysis
 * - Cloud Storage bucket discovery and public access detection
 * - IAM role/policy enumeration and overprivilege detection
 * - VPC firewall rule analysis
 * - Service account security audit
 * 
 * ## Prerequisites
 * - gcloud CLI installed (via Termux: `pip install gcloud`)
 * - GCP credentials configured (`gcloud auth login`)
 * - Active GCP project set (`gcloud config set project PROJECT_ID`)
 * - Network connectivity to GCP APIs
 * 
 * ## Usage Examples
 * 
 * ### Basic: Scan all resources in default project
 * ```
 * gcp asset
 * ```
 * 
 * ### Scan specific project
 * ```
 * gcp asset --project_id my-project
 * ```
 * 
 * ### Scan specific resource type
 * ```
 * gcp asset --resource_type compute
 * gcp asset --resource_type storage
 * gcp asset --resource_type iam
 * gcp asset --resource_type firewall
 * ```
 * 
 * ## Security Considerations
 * - Requires active GCP credentials (user or service account)
 * - Operations are read-only (enumeration and configuration analysis)
 * - Commands run within the user's terminal/shell context
 * - Standard GCP IAM controls restrict what can be discovered
 * 
 * @see CloudTool Base class for cloud provider abstraction
 */
class GCPAssetTool @Inject constructor(
    private val shell: ShellExecutor,
) : BaseTool() {

    override val metadata = ToolMetadata(
        name = "gcp_asset_tool",
        displayName = "GCP Asset Scanner",
        description = "Enumerate and analyze Google Cloud Platform infrastructure for security misconfigurations. Scans Compute Engine, Cloud Storage, IAM, and firewall rules.",
        category = ToolCategory.CLOUD_SEC,
        parameters = listOf(
            ToolParam(
                name = "project_id",
                type = ParamType.STRING,
                description = "GCP project ID (default: uses active project)",
                required = false,
                defaultValue = "",
            ),
            ToolParam(
                name = "resource_type",
                type = ParamType.ENUM,
                description = "Resource type to scan",
                required = false,
                defaultValue = "all",
                enumValues = listOf("all", "compute", "storage", "iam", "firewall")
            )
        ),
        supportedPlatforms = listOf("Android", "Linux", "macOS", "Windows"),
        apiKeyRef = "gcp_credentials",
        chainSuggestions = listOf(
            "aws_inspector",
            "azure_ad_tool",
            "kube_hunter",
            "shodan_search"
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val projectId = params["project_id"] as? String ?: ""
        val resourceType = params["resource_type"] as? String ?: "all"

        emit(ToolEvent.Log("🔍 GCP Asset Scanner starting..."))
        emit(ToolEvent.Log("Resource Type: $resourceType"))
        emit(ToolEvent.Log(""))

        // Check gcloud CLI availability
        val gcloudCheck = shell.execute("which gcloud || echo NOT_FOUND")
        if (gcloudCheck.contains("NOT_FOUND")) {
            emit(ToolEvent.Error("gcloud CLI not found. Install: pip install gcloud"))
            emit(ToolEvent.ChainSuggestion("shodan_search", mapOf("query" to "cloud")))
            return
        }

        // Get or verify project ID
        val activeProject = if (projectId.isBlank()) {
            val projectCmd = "gcloud config get-value project 2>/dev/null"
            val output = shell.execute(projectCmd)
            if (output.isBlank() || output.contains("(unset)")) {
                emit(ToolEvent.Error("No active project. Run: gcloud config set project PROJECT_ID"))
                emit(ToolEvent.ChainSuggestion("shodan_search", mapOf("query" to "cloud")))
                return
            }
            output.trim()
        } else {
            projectId
        }

        emit(ToolEvent.Log("📋 Verifying GCP credentials..."))
        emit(ToolEvent.Log("Project: $activeProject"))
        
        // Verify authentication
        val authCheck = shell.execute("gcloud auth list --filter=status:ACTIVE --format='value(account)'")
        if (authCheck.isBlank()) {
            emit(ToolEvent.Error("Not authenticated. Run: gcloud auth login"))
            emit(ToolEvent.ChainSuggestion("shodan_search", mapOf("query" to "cloud")))
            return
        }

        emit(ToolEvent.Log("✅ Authenticated as: ${authCheck.trim()}"))
        emit(ToolEvent.Log(""))

        // Execute scans based on resource type
        val findings = mutableListOf<Finding>()

        when (resourceType) {
            "compute" -> findings.addAll(scanCompute(activeProject))
            "storage" -> findings.addAll(scanStorage(activeProject))
            "iam" -> findings.addAll(scanIAM(activeProject))
            "firewall" -> findings.addAll(scanFirewall(activeProject))
            "all" -> {
                emit(ToolEvent.Log("🔍 Scanning all GCP resources..."))
                emit(ToolEvent.Log(""))
                findings.addAll(scanCompute(activeProject))
                findings.addAll(scanStorage(activeProject))
                findings.addAll(scanIAM(activeProject))
                findings.addAll(scanFirewall(activeProject))
            }
            else -> {
                emit(ToolEvent.Error("Invalid resource_type: $resourceType"))
                emit(ToolEvent.ChainSuggestion("shodan_search", mapOf("query" to "cloud")))
                return
            }
        }

        // Emit findings summary
        emit(ToolEvent.Log(""))
        emit(ToolEvent.Log("=" * 60))
        emit(ToolEvent.Log("FINDINGS SUMMARY"))
        emit(ToolEvent.Log("=" * 60))

        val critical = findings.count { it.severity == Severity.CRITICAL }
        val high = findings.count { it.severity == Severity.HIGH }
        val medium = findings.count { it.severity == Severity.MEDIUM }
        val low = findings.count { it.severity == Severity.LOW }

        emit(ToolEvent.Log("Total Findings: ${findings.size}"))
        emit(ToolEvent.Log("  🔴 Critical: $critical"))
        emit(ToolEvent.Log("  🟠 High: $high"))
        emit(ToolEvent.Log("  🟡 Medium: $medium"))
        emit(ToolEvent.Log("  🟢 Low: $low"))
        emit(ToolEvent.Log(""))

        // Emit detailed findings
        findings.sortedByDescending { it.severity }.forEach { finding ->
            val emoji = when (finding.severity) {
                Severity.CRITICAL -> "🔴"
                Severity.HIGH -> "🟠"
                Severity.MEDIUM -> "🟡"
                Severity.LOW -> "🟢"
            }
            emit(ToolEvent.Log("$emoji [${finding.severity}] ${finding.title}"))
            emit(ToolEvent.Log("   Resource: ${finding.resource}"))
            emit(ToolEvent.Log("   Issue: ${finding.description}"))
            if (finding.remediation.isNotEmpty()) {
                emit(ToolEvent.Log("   Fix: ${finding.remediation}"))
            }
            emit(ToolEvent.Log(""))
        }

        // Recommendations
        emit(ToolEvent.Log("=" * 60))
        emit(ToolEvent.Log("NEXT STEPS"))
        emit(ToolEvent.Log("=" * 60))
        emit(ToolEvent.Log(""))
        emit(ToolEvent.Log("Consider running:"))
        emit(ToolEvent.Log("  • 'aws inspect' - Check AWS resources if multi-cloud"))
        emit(ToolEvent.Log("  • 'azure ad' - Enumerate Azure infrastructure"))
        emit(ToolEvent.Log("  • 'kube hunter' - Scan for exposed GKE clusters"))
        emit(ToolEvent.Log("  • 'shodan <gcp-ip>' - Check external exposure"))

        emit(ToolEvent.Log("✅ GCP asset scan complete"))
        emit(ToolEvent.ChainSuggestion("shodan_search", mapOf("query" to "cloud")))
    }

    /**
     * Scans Compute Engine instances for security issues.
     */
    private suspend fun scanCompute(project: String): List<Finding> {
        val findings = mutableListOf<Finding>()
        
        val cmd = "gcloud compute instances list --project=$project --format=json"
        val output = shell.execute(cmd)
        
        if (output.contains("ERROR") || output.isBlank()) {
            return findings
        }

        // Parse instances (simplified - would use proper JSON parser)
        val instances = parseJsonArray(output)
        
        instances.forEach { instance ->
            val name = instance["name"] as? String ?: return@forEach
            val zone = (instance["zone"] as? String)?.substringAfterLast("/") ?: "unknown"
            val networkInterfaces = instance["networkInterfaces"] as? List<*> ?: emptyList<Any>()
            
            // Check for external IP addresses
            networkInterfaces.forEach { iface ->
                val ifaceMap = iface as? Map<*, *> ?: return@forEach
                val accessConfigs = ifaceMap["accessConfigs"] as? List<*> ?: return@forEach
                
                if (accessConfigs.isNotEmpty()) {
                    findings.add(Finding(
                        severity = Severity.MEDIUM,
                        title = "Compute Instance with External IP",
                        resource = "Instance: $name (Zone: $zone)",
                        description = "Instance has an external IP address. Ensure firewall rules are properly configured.",
                        remediation = "Review firewall rules. Consider using Cloud NAT or Identity-Aware Proxy instead of direct public access."
                    ))
                }
            }

            // Check for attached service accounts with default credentials
            val serviceAccounts = instance["serviceAccounts"] as? List<*> ?: emptyList<Any>()
            serviceAccounts.forEach { sa ->
                val saMap = sa as? Map<*, *> ?: return@forEach
                val email = saMap["email"] as? String ?: return@forEach
                
                if (email.contains("-compute@developer.gserviceaccount.com")) {
                    findings.add(Finding(
                        severity = Severity.LOW,
                        title = "Instance Using Default Service Account",
                        resource = "Instance: $name",
                        description = "Instance uses the default Compute Engine service account. Consider using a dedicated service account with minimal permissions.",
                        remediation = "Create a custom service account and attach it to the instance."
                    ))
                }
            }
        }

        return findings
    }

    /**
     * Scans Cloud Storage buckets for public access.
     */
    private suspend fun scanStorage(project: String): List<Finding> {
        val findings = mutableListOf<Finding>()
        
        val cmd = "gsutil ls -p $project"
        val output = shell.execute(cmd)
        
        if (output.contains("ERROR")) {
            return findings
        }

        val buckets = output.lines().filter { it.startsWith("gs://") }
        
        buckets.forEach { bucketUrl ->
            val bucketName = bucketUrl.removePrefix("gs://").removeSuffix("/")
            
            // Check IAM policy for public access
            val iamCmd = "gsutil iam get $bucketUrl"
            val iamOutput = shell.execute(iamCmd)
            
            if (iamOutput.contains("allUsers") || iamOutput.contains("allAuthenticatedUsers")) {
                findings.add(Finding(
                    severity = Severity.CRITICAL,
                    title = "Publicly Accessible Storage Bucket",
                    resource = "Bucket: $bucketName",
                    description = "Bucket grants access to allUsers or allAuthenticatedUsers. Data may be exposed to the internet.",
                    remediation = "Run: gsutil iam ch -d allUsers $bucketUrl && gsutil iam ch -d allAuthenticatedUsers $bucketUrl"
                ))
            }

            // Check for uniform bucket-level access
            val metadataCmd = "gsutil uniformbucketlevelaccess get $bucketUrl"
            val metadataOutput = shell.execute(metadataCmd)
            
            if (metadataOutput.contains("Disabled")) {
                findings.add(Finding(
                    severity = Severity.LOW,
                    title = "Bucket Not Using Uniform Access",
                    resource = "Bucket: $bucketName",
                    description = "Bucket uses legacy ACLs instead of uniform bucket-level access. This makes access control harder to manage.",
                    remediation = "Enable uniform bucket-level access: gsutil uniformbucketlevelaccess set on $bucketUrl"
                ))
            }
        }

        return findings
    }

    /**
     * Scans IAM for overprivileged roles and service accounts.
     */
    private suspend fun scanIAM(project: String): List<Finding> {
        val findings = mutableListOf<Finding>()
        
        // List IAM policy bindings
        val cmd = "gcloud projects get-iam-policy $project --format=json"
        val output = shell.execute(cmd)
        
        if (output.contains("ERROR")) {
            return findings
        }

        val policy = parseJson(output)
        val bindings = policy["bindings"] as? List<*> ?: emptyList<Any>()
        
        bindings.forEach { binding ->
            val bindingMap = binding as? Map<*, *> ?: return@forEach
            val role = bindingMap["role"] as? String ?: return@forEach
            val members = bindingMap["members"] as? List<*> ?: emptyList<Any>()
            
            // Check for Owner/Editor roles
            if (role.contains("roles/owner") || role.contains("roles/editor")) {
                members.forEach { member ->
                    val memberStr = member as? String ?: return@forEach
                    
                    findings.add(Finding(
                        severity = Severity.HIGH,
                        title = "Overprivileged IAM Binding",
                        resource = "Member: $memberStr",
                        description = "Has $role role which grants extensive permissions. Follow principle of least privilege.",
                        remediation = "Replace with more specific roles like roles/viewer, roles/compute.admin, etc."
                    ))
                }
            }

            // Check for allUsers or allAuthenticatedUsers
            if (members.any { it == "allUsers" || it == "allAuthenticatedUsers" }) {
                findings.add(Finding(
                    severity = Severity.CRITICAL,
                    title = "Public IAM Binding",
                    resource = "Role: $role",
                    description = "Role is granted to allUsers or allAuthenticatedUsers, making project resources publicly accessible.",
                    remediation = "Remove public access: gcloud projects remove-iam-policy-binding $project --member=allUsers --role=$role"
                ))
            }
        }

        return findings
    }

    /**
     * Scans VPC firewall rules for overly permissive configurations.
     */
    private suspend fun scanFirewall(project: String): List<Finding> {
        val findings = mutableListOf<Finding>()
        
        val cmd = "gcloud compute firewall-rules list --project=$project --format=json"
        val output = shell.execute(cmd)
        
        if (output.contains("ERROR")) {
            return findings
        }

        val rules = parseJsonArray(output)
        
        rules.forEach { rule ->
            val name = rule["name"] as? String ?: return@forEach
            val direction = rule["direction"] as? String ?: "INGRESS"
            val sourceRanges = rule["sourceRanges"] as? List<*> ?: emptyList<Any>()
            val allowed = rule["allowed"] as? List<*> ?: emptyList<Any>()
            
            if (direction == "INGRESS" && sourceRanges.contains("0.0.0.0/0")) {
                allowed.forEach { allowRule ->
                    val allowMap = allowRule as? Map<*, *> ?: return@forEach
                    val protocol = allowMap["IPProtocol"] as? String ?: return@forEach
                    val ports = allowMap["ports"] as? List<*> ?: listOf("all")
                    
                    val portsStr = ports.joinToString(", ")
                    val severity = when {
                        ports.any { it == "22" || it == "3389" } -> Severity.CRITICAL
                        ports.any { it == "80" || it == "443" } -> Severity.MEDIUM
                        protocol.equals("all", ignoreCase = true) -> Severity.HIGH
                        else -> Severity.LOW
                    }
                    
                    findings.add(Finding(
                        severity = severity,
                        title = "Firewall Rule Allows 0.0.0.0/0 Ingress",
                        resource = "Rule: $name",
                        description = "Allows ingress from anywhere (0.0.0.0/0) on protocol $protocol, ports: $portsStr",
                        remediation = "Restrict source ranges to specific IP blocks or use Identity-Aware Proxy."
                    ))
                }
            }
        }

        return findings
    }

    // ──────────────────────────────────────────────────────────────────
    // Helper Functions
    // ──────────────────────────────────────────────────────────────────

    private fun parseJson(json: String): Map<String, Any> {
        // Simplified JSON parsing - in production, use Gson/kotlinx.serialization
        return emptyMap()
    }

    private fun parseJsonArray(json: String): List<Map<String, Any>> {
        // Simplified JSON parsing - in production, use Gson/kotlinx.serialization
        return emptyList()
    }

    private operator fun String.times(n: Int): String = this.repeat(n)

    // ──────────────────────────────────────────────────────────────────
    // Data Models
    // ──────────────────────────────────────────────────────────────────

    data class Finding(
        val severity: Severity,
        val title: String,
        val resource: String,
        val description: String,
        val remediation: String = "",
    )

    enum class Severity {
        CRITICAL, HIGH, MEDIUM, LOW
    }
}
