import re
from pathlib import Path
from typing import List, Tuple

from brox.model import Capability, Component, Property, generate_bom_ref


CLOUD_STORAGE = {
    "s3": {
        "patterns": [
            r"import\s+boto3",
            r"from\s+boto3\s+import",
            r"boto3\.client\(['\"]s3['\"]\)",
            r"boto3\.resource\(['\"]s3['\"]\)",
            r"s3_client\s*=",
            r"s3_resource\s*=",
        ],
        "write_patterns": [
            r"\.put_object\(",
            r"\.upload_file\(",
            r"\.upload_fileobj\(",
            r"\.copy\(",
        ],
        "read_patterns": [
            r"\.get_object\(",
            r"\.download_file\(",
            r"\.download_fileobj\(",
            r"\.list_objects",
        ],
        "capability_prefix": "storage.s3",
    },
    "gcs": {
        "patterns": [
            r"from\s+google\.cloud\s+import\s+storage",
            r"google\.cloud\.storage",
            r"storage\.Client\(",
            r"storage\.Bucket\(",
        ],
        "write_patterns": [
            r"\.upload_from",
            r"\.upload_blob",
            r"blob\.upload",
        ],
        "read_patterns": [
            r"\.download_to",
            r"\.download_as",
            r"blob\.download",
            r"bucket\.list_blobs",
        ],
        "capability_prefix": "storage.gcs",
    },
    "azure_blob": {
        "patterns": [
            r"from\s+azure\.storage\.blob\s+import",
            r"azure\.storage\.blob",
            r"BlobServiceClient",
            r"ContainerClient",
            r"BlobClient",
        ],
        "write_patterns": [
            r"\.upload_blob\(",
            r"\.upload_data\(",
            r"\.create_blob\(",
        ],
        "read_patterns": [
            r"\.download_blob\(",
            r"\.download_to_stream\(",
            r"\.list_blobs\(",
        ],
        "capability_prefix": "storage.azure",
    },
}


def detect_cloud_storage(repo_path: Path) -> Tuple[List[Component], List[Capability]]:
    components = []
    capabilities = []

    code_patterns = ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx", "*.go", "*.java"]
    code_files = []
    for pattern in code_patterns:
        code_files.extend(repo_path.rglob(pattern))

    detected = {}

    for code_file in code_files:
        should_skip = False
        exclusions = ["node_modules", ".git", "venv", "__pycache__", "dist", "build"]
        for part in code_file.parts:
            if part in exclusions:
                should_skip = True
                break

        if should_skip:
            continue

        try:
            storage_items = detect_storage_in_file(code_file, repo_path)
            for provider, has_read, has_write, evidence in storage_items:
                key = (provider, has_read, has_write)
                if key not in detected:
                    detected[key] = evidence
                    component, caps = create_storage_component(
                        provider, has_read, has_write, evidence
                    )
                    components.append(component)
                    capabilities.extend(caps)
        except Exception as e:
            print(f"Warning: Failed to detect cloud storage in {code_file}: {e}")

    return components, capabilities


def detect_storage_in_file(
    code_file: Path, repo_root: Path
) -> List[Tuple[str, bool, bool, str]]:
    detected = []

    try:
        with open(code_file, encoding="utf-8", errors="ignore") as f:
            content = f.read()
    except Exception:
        return detected

    rel_path = str(code_file.relative_to(repo_root))

    for provider, config in CLOUD_STORAGE.items():
        provider_found = False
        evidence_line = 0

        for pattern in config["patterns"]:
            matches = list(re.finditer(pattern, content))
            if matches:
                provider_found = True
                evidence_line = content[: matches[0].start()].count("\n") + 1
                break

        if not provider_found:
            continue

        has_read = False
        has_write = False

        for pattern in config["read_patterns"]:
            if re.search(pattern, content):
                has_read = True
                break

        for pattern in config["write_patterns"]:
            if re.search(pattern, content):
                has_write = True
                break

        if not has_read and not has_write:
            has_read = True
            has_write = True

        evidence = f"{rel_path}:{evidence_line}"
        detected.append((provider, has_read, has_write, evidence))

    return detected


def create_storage_component(
    provider: str, has_read: bool, has_write: bool, evidence: str
) -> Tuple[Component, List[Capability]]:
    bom_ref = generate_bom_ref("cloud_storage", provider)

    properties = [
        Property(name="brox.ai.asset.kind", value="cloud_storage"),
        Property(name="brox.cloud.provider", value=provider),
        Property(name="brox.location.file", value=evidence.split(":")[0]),
    ]

    component = Component(
        type="service",
        name=f"cloud-storage:{provider}",
        version="1",
        bom_ref=bom_ref,
        properties=properties,
    )

    capabilities = []
    config = CLOUD_STORAGE[provider]
    cap_prefix = config["capability_prefix"]

    if has_read:
        capabilities.append(
            Capability(
                name=f"{cap_prefix}.read",
                scope="*",
                asset=bom_ref,
                evidence=[evidence],
            )
        )

    if has_write:
        capabilities.append(
            Capability(
                name=f"{cap_prefix}.write",
                scope="*",
                asset=bom_ref,
                evidence=[evidence],
            )
        )

    return component, capabilities
