"""File-based CloudTrail event ingester for local JSON files."""

import gzip
import json
from collections.abc import Iterator
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, cast

from ..models.cloudtrail import CloudTrailEvent
from .base import CloudTrailIngester

# re-export for backwards compatibility
__all__ = ["FileIngester", "FileIngesterError"]


class FileIngesterError(Exception):
    """Raised when file ingestion fails."""

    pass


class FileIngester(CloudTrailIngester):
    """
    Ingests CloudTrail events from local JSON files.

    Supports:
    - Single JSON file
    - Directory of JSON files
    - Gzipped JSON files (*.json.gz)
    - Standard CloudTrail log format
    """

    def __init__(self, file_paths: str | Path | list[str | Path]):
        """
        Initialize the file ingester.

        Args:
            file_paths: Path to a file, directory, or list of file paths
        """
        if isinstance(file_paths, (str, Path)):
            file_paths = [file_paths]

        self.file_paths: list[Path] = [Path(p) for p in file_paths]
        self._validate_paths()

    def _validate_paths(self) -> None:
        """Validate that all provided paths exist."""
        for path in self.file_paths:
            if not path.exists():
                raise FileNotFoundError(f"File or directory not found: {path}")

    def _get_all_files(self) -> list[Path]:
        """
        Get all JSON files from the provided paths.

        Returns:
            List of JSON file paths
        """
        all_files: list[Path] = []

        for path in self.file_paths:
            if path.is_file():
                if path.suffix in (".json", ".gz"):
                    all_files.append(path)
                else:
                    raise FileIngesterError(
                        f"Unsupported file type: {path}. Expected .json or .json.gz"
                    )
            elif path.is_dir():
                # Find all JSON files in directory
                json_files = list(path.glob("*.json"))
                gz_files = list(path.glob("*.json.gz"))
                all_files.extend(json_files + gz_files)
            else:
                raise FileIngesterError(f"Invalid path: {path}")

        if not all_files:
            raise FileIngesterError("No JSON files found in the provided paths")

        return sorted(all_files)

    def _read_file(self, file_path: Path) -> dict[str, Any]:
        """
        Read and parse a JSON file (supports gzip).

        Args:
            file_path: Path to the JSON file

        Returns:
            Parsed JSON data

        Raises:
            FileIngesterError: If file cannot be read or parsed
        """
        try:
            if file_path.suffix == ".gz":
                with gzip.open(file_path, "rt", encoding="utf-8") as f:
                    return cast(dict[str, Any], json.load(f))
            else:
                with open(file_path, encoding="utf-8") as f:
                    return cast(dict[str, Any], json.load(f))
        except json.JSONDecodeError as e:
            raise FileIngesterError(f"Invalid JSON in file {file_path}: {e}") from e
        except Exception as e:
            raise FileIngesterError(f"Error reading file {file_path}: {e}") from e

    def _parse_cloudtrail_event(self, event_dict: dict[str, Any]) -> CloudTrailEvent:
        """
        Parse a CloudTrail event dictionary into a CloudTrailEvent object.

        Args:
            event_dict: Dictionary representation of a CloudTrail event

        Returns:
            CloudTrailEvent object

        Raises:
            FileIngesterError: If event cannot be parsed
        """
        try:
            return CloudTrailEvent.from_dict(event_dict)
        except Exception as e:
            raise FileIngesterError(f"Error parsing CloudTrail event: {e}") from e

    def fetch_events(
        self,
        start_time: datetime,
        end_time: datetime,
        filters: dict[str, Any] | None = None,
    ) -> Iterator[CloudTrailEvent]:
        """
        Fetch CloudTrail events from local files within the time range.

        Args:
            start_time: Start of the time range (inclusive)
            end_time: End of the time range (inclusive)
            filters: Optional filters (not implemented for file ingester)

        Yields:
            CloudTrailEvent objects within the time range

        Raises:
            FileIngesterError: If files cannot be read or parsed
        """

        # Ensure start_time and end_time are timezone-aware (UTC)
        if start_time.tzinfo is None:
            start_time = start_time.replace(tzinfo=timezone.utc)
        if end_time.tzinfo is None:
            end_time = end_time.replace(tzinfo=timezone.utc)

        all_files = self._get_all_files()

        for file_path in all_files:
            data = self._read_file(file_path)

            # CloudTrail log files have a "Records" array
            # Also support raw arrays of events
            if "Records" in data:
                events = data["Records"]
            elif isinstance(data, list):
                events = data
            elif isinstance(data, dict) and any(
                k in data for k in ("eventTime", "eventName", "eventSource")
            ):
                # Single event
                events = [data]
            else:
                raise FileIngesterError(
                    f"Unexpected CloudTrail file format in {file_path}. "
                    "Expected 'Records' array or array of events."
                )

            for event_dict in events:
                try:
                    event = self._parse_cloudtrail_event(event_dict)

                    # Filter by time range
                    if start_time <= event.event_time <= end_time:
                        yield event

                except FileIngesterError:
                    # Skip invalid events but continue processing
                    continue

    def get_event_count(self) -> int:
        """
        Get total count of events in all files (without time filtering).

        Returns:
            Total number of events

        Raises:
            FileIngesterError: If files cannot be read
        """
        count = 0
        all_files = self._get_all_files()

        for file_path in all_files:
            data = self._read_file(file_path)

            if "Records" in data:
                count += len(data["Records"])
            elif isinstance(data, list):
                count += len(data)
            elif isinstance(data, dict):
                count += 1

        return count
