from __future__ import annotations

import os
from dataclasses import dataclass
from typing import Any

import boto3
from botocore.exceptions import BotoCoreError, NoCredentialsError, ProfileNotFound

from awspipe.exceptions import AwspipeError


@dataclass(frozen=True)
class AwsSessionContext:
    session: Any
    profile: str | None
    region: str
    account_id: str
    caller_identity: dict[str, Any]


@dataclass(frozen=True)
class DoctorStatus:
    profile: str | None
    region: str
    account_id: str
    arn: str
    cache_path: str


class SessionFactory:
    def __init__(self, session_cls: Any | None = None) -> None:
        self._session_cls = session_cls or boto3.session.Session

    def create(self, profile: str | None, region: str | None) -> Any:
        try:
            session = self._session_cls(profile_name=profile, region_name=region)
        except ProfileNotFound as exc:
            raise AwspipeError(f"AWS profile not found: {profile}") from exc
        return session

    def resolve_region(self, session: Any, region: str | None) -> str:
        return region or session.region_name or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") or "us-east-1"

    def resolve_regions(self, session: Any, region: str | None, all_regions: bool = False) -> list[str]:
        resolved_region = self.resolve_region(session, region)
        if not all_regions:
            return [resolved_region]

        client = session.client("ec2", region_name=resolved_region)
        response = client.describe_regions(AllRegions=False)
        return [item["RegionName"] for item in response.get("Regions", [])]

    def resolve_context(self, profile: str | None, region: str | None) -> AwsSessionContext:
        session = self.create(profile=profile, region=region)
        resolved_region = self.resolve_region(session, region)
        try:
            identity = session.client("sts", region_name=resolved_region).get_caller_identity()
        except (BotoCoreError, NoCredentialsError) as exc:
            raise AwspipeError(f"Unable to resolve AWS credentials: {exc}") from exc
        return AwsSessionContext(
            session=session,
            profile=profile,
            region=resolved_region,
            account_id=identity["Account"],
            caller_identity=identity,
        )

    def doctor(self, profile: str | None, region: str | None, cache_path: str) -> DoctorStatus:
        context = self.resolve_context(profile=profile, region=region)
        return DoctorStatus(
            profile=context.profile,
            region=context.region,
            account_id=context.account_id,
            arn=context.caller_identity["Arn"],
            cache_path=cache_path,
        )
