"""
FGIP Source Artifact Management - SHA256 hashing, URL fetching, verification.

All sources are stored by SHA256 hash for deduplication and verification.
"""

import hashlib
import json
import shutil
import urllib.request
import urllib.error
from datetime import datetime
from pathlib import Path
from typing import Optional, Tuple
import uuid


def hash_file(path: Path) -> str:
    """
    Compute SHA256 hash of a file.

    Args:
        path: Path to the file

    Returns:
        Hex-encoded SHA256 hash
    """
    sha256 = hashlib.sha256()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(65536), b''):
            sha256.update(chunk)
    return sha256.hexdigest()


def hash_bytes(data: bytes) -> str:
    """Compute SHA256 hash of bytes."""
    return hashlib.sha256(data).hexdigest()


def store_source(file_path: Path, sources_dir: Path, copy: bool = True) -> Tuple[str, str]:
    """
    Store a source file by SHA256 hash.

    Args:
        file_path: Path to the source file
        sources_dir: Directory to store sources
        copy: If True, copy file. If False, move file.

    Returns:
        Tuple of (source_id, sha256)
    """
    sources_dir = Path(sources_dir)
    sources_dir.mkdir(parents=True, exist_ok=True)

    # Compute hash
    sha256 = hash_file(file_path)

    # Determine storage path (SHA256-named)
    # Use first 2 chars as subdirectory for filesystem efficiency
    subdir = sources_dir / sha256[:2]
    subdir.mkdir(exist_ok=True)

    # Preserve extension
    ext = file_path.suffix or ""
    stored_path = subdir / f"{sha256}{ext}"

    # Store if not already present
    if not stored_path.exists():
        if copy:
            shutil.copy2(file_path, stored_path)
        else:
            shutil.move(file_path, stored_path)

    # Generate source_id
    source_id = f"src_{sha256[:16]}"

    return source_id, sha256


def fetch_url(url: str, sources_dir: Path, timeout: int = 30) -> Tuple[str, str, str]:
    """
    Fetch a URL and store its content by SHA256.

    Uses stdlib urllib only (no external dependencies).

    Args:
        url: URL to fetch
        sources_dir: Directory to store fetched content
        timeout: Request timeout in seconds

    Returns:
        Tuple of (source_id, sha256, file_path relative to sources_dir)

    Raises:
        urllib.error.URLError: On fetch failure
    """
    sources_dir = Path(sources_dir)
    sources_dir.mkdir(parents=True, exist_ok=True)

    # Fetch content
    request = urllib.request.Request(
        url,
        headers={'User-Agent': 'FGIP-CausalityEngine/1.0'}
    )

    with urllib.request.urlopen(request, timeout=timeout) as response:
        content = response.read()
        content_type = response.headers.get('Content-Type', '')

    # Compute hash
    sha256 = hash_bytes(content)

    # Determine extension from content type
    ext = _content_type_to_ext(content_type)

    # Store
    subdir = sources_dir / sha256[:2]
    subdir.mkdir(exist_ok=True)
    stored_path = subdir / f"{sha256}{ext}"

    if not stored_path.exists():
        with open(stored_path, 'wb') as f:
            f.write(content)

    # Generate source_id
    source_id = f"src_{sha256[:16]}"

    # Return relative path
    rel_path = str(stored_path.relative_to(sources_dir))

    return source_id, sha256, rel_path


def _content_type_to_ext(content_type: str) -> str:
    """Map content type to file extension."""
    ct = content_type.lower().split(';')[0].strip()
    mapping = {
        'text/html': '.html',
        'text/plain': '.txt',
        'application/pdf': '.pdf',
        'application/json': '.json',
        'text/xml': '.xml',
        'application/xml': '.xml',
        'image/png': '.png',
        'image/jpeg': '.jpg',
        'image/gif': '.gif',
    }
    return mapping.get(ct, '.bin')


def verify_source(source_id: str, sources_dir: Path, expected_sha256: str) -> bool:
    """
    Verify a stored source matches its expected SHA256.

    Args:
        source_id: Source ID (used to locate file)
        sources_dir: Sources directory
        expected_sha256: Expected SHA256 hash

    Returns:
        True if hash matches, False otherwise
    """
    sources_dir = Path(sources_dir)

    # Find file by SHA256 prefix (subdirectory structure)
    subdir = sources_dir / expected_sha256[:2]
    if not subdir.exists():
        return False

    # Look for file with matching hash prefix
    for path in subdir.glob(f"{expected_sha256}*"):
        actual_sha256 = hash_file(path)
        if actual_sha256 == expected_sha256:
            return True

    return False


def get_source_path(sha256: str, sources_dir: Path) -> Optional[Path]:
    """
    Get the path to a stored source by its SHA256.

    Args:
        sha256: SHA256 hash of the source
        sources_dir: Sources directory

    Returns:
        Path to the source file, or None if not found
    """
    sources_dir = Path(sources_dir)
    subdir = sources_dir / sha256[:2]

    if not subdir.exists():
        return None

    for path in subdir.glob(f"{sha256}*"):
        return path

    return None


def create_source_receipt(
    operation: str,
    source_id: str,
    sha256: str,
    url: Optional[str] = None,
    file_path: Optional[str] = None,
) -> dict:
    """
    Create a receipt for a source operation.

    Args:
        operation: Operation type (store, fetch, verify)
        source_id: Source ID
        sha256: Content SHA256
        url: Source URL (if fetched)
        file_path: File path (if stored)

    Returns:
        Receipt dict
    """
    receipt = {
        "receipt_id": f"rcpt_{uuid.uuid4().hex[:16]}",
        "operation": operation,
        "source_id": source_id,
        "sha256": sha256,
        "timestamp": datetime.utcnow().isoformat(),
    }

    if url:
        receipt["url"] = url
    if file_path:
        receipt["file_path"] = str(file_path)

    return receipt
