"""Pytest configuration and shared fixtures."""

import json
from datetime import UTC, datetime
from pathlib import Path

import boto3
import pytest
from moto import mock_aws

from augint_billing_lib.models import Link
from augint_billing_lib.service import BillingService


# Mock implementations
class MockStripePort:
    """Mock Stripe port for testing."""

    def __init__(self):
        self.customers = {}
        self.subscriptions = {}
        self.usage_reports = []
        self.has_pm = True
        self.sub_item = "si_test999"

    def has_default_payment_method(self, customer_id: str) -> bool:
        """Check if customer has default payment method."""
        return self.customers.get(customer_id, {}).get("has_pm", self.has_pm)

    def ensure_metered_subscription(self, customer_id: str) -> str:
        """Ensure customer has metered subscription."""
        self.subscriptions[customer_id] = self.sub_item
        return self.sub_item

    def cancel_subscription_if_any(self, customer_id: str) -> None:
        """Cancel subscription if exists."""
        self.subscriptions.pop(customer_id, None)

    def report_usage(
        self, subscription_item_id: str, units: int, timestamp: int, idempotency_key: str
    ) -> dict:
        """Report usage to Stripe."""
        report = {
            "subscription_item_id": subscription_item_id,
            "units": units,
            "timestamp": timestamp,
            "idempotency_key": idempotency_key,
        }
        self.usage_reports.append(report)
        # Return a mock Stripe response
        return {
            "id": f"ur_test_{len(self.usage_reports)}",
            "object": "usage_record",
            "subscription_item": subscription_item_id,
            "quantity": units,
        }


class MockCustomerRepo:
    """Mock customer repository for testing."""

    def __init__(self):
        self.links = {}

    def get_by_api_key(self, api_key_id: str) -> Link:
        """Get link by API key."""
        if api_key_id not in self.links:
            raise KeyError(f"API key not found: {api_key_id}")
        return self.links[api_key_id]

    def get_by_customer(self, customer_id: str) -> list[Link]:
        """Get links by customer ID."""
        return [link for link in self.links.values() if link.stripe_customer_id == customer_id]

    def save(self, link: Link) -> None:
        """Save link."""
        self.links[link.api_key_id] = link

    def scan_metered(self) -> list[Link]:
        """Scan for metered customers."""
        return [
            link
            for link in self.links.values()
            if link.plan == "metered" and link.metered_subscription_item_id
        ]


class MockUsageSource:
    """Mock usage source for testing."""

    def __init__(self, default_usage: int = 100):
        self.default_usage = default_usage
        self.usage_data = {}

    def get_usage(
        self, usage_plan_id: str, api_key_id: str, since: datetime, until: datetime
    ) -> int | None:
        """Get usage for API key."""
        key = f"{usage_plan_id}:{api_key_id}"
        return self.usage_data.get(key, self.default_usage)

    def set_usage(self, usage_plan_id: str, api_key_id: str, units: int):
        """Set usage for testing."""
        key = f"{usage_plan_id}:{api_key_id}"
        self.usage_data[key] = units


class MockPlanAdmin:
    """Mock plan admin for testing."""

    def __init__(self):
        self.key_plans = {}

    def move_key_to_plan(self, api_key_id: str, usage_plan_id: str) -> None:
        """Move API key to usage plan."""
        self.key_plans[api_key_id] = usage_plan_id


# Fixtures
@pytest.fixture
def mock_stripe():
    """Mock Stripe port."""
    return MockStripePort()


@pytest.fixture
def mock_repo():
    """Mock customer repository."""
    return MockCustomerRepo()


@pytest.fixture
def mock_usage():
    """Mock usage source."""
    return MockUsageSource()


@pytest.fixture
def mock_plan_admin():
    """Mock plan admin."""
    return MockPlanAdmin()


@pytest.fixture
def billing_service(mock_repo, mock_stripe, mock_usage, mock_plan_admin):
    """Billing service with mocked dependencies."""
    return BillingService(
        repo=mock_repo, stripe=mock_stripe, usage=mock_usage, plan_admin=mock_plan_admin
    )


@pytest.fixture
def sample_links():
    """Sample Link objects for testing."""
    return [
        Link(
            api_key_id="key_free123",
            stripe_customer_id="cus_free456",
            plan="free",
            usage_plan_id="FREE_10K",
        ),
        Link(
            api_key_id="key_metered789",
            stripe_customer_id="cus_metered012",
            plan="metered",
            usage_plan_id="METERED",
            metered_subscription_item_id="si_test999",
            last_reported_usage_ts=datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC),
            current_month_reported_units=5000,
        ),
        Link(
            api_key_id="key_metered_no_sub",
            stripe_customer_id="cus_nosub",
            plan="metered",
            usage_plan_id="METERED",
            metered_subscription_item_id=None,  # Missing subscription
        ),
    ]


@pytest.fixture
def populated_repo(mock_repo, sample_links):
    """Repository populated with sample data."""
    for link in sample_links:
        mock_repo.save(link)
    return mock_repo


@pytest.fixture
def stripe_event_fixtures():
    """Load Stripe event fixtures from JSON files."""
    fixtures_dir = Path(__file__).parent / "fixtures" / "stripe_events"
    events = {}
    for file in fixtures_dir.glob("*.json"):
        with file.open() as f:
            events[file.stem] = json.load(f)
    return events


@pytest.fixture
def ddb_table():
    """DynamoDB table for testing."""
    with mock_aws():
        ddb = boto3.resource("dynamodb", region_name="us-east-1")
        table = ddb.create_table(
            TableName="test-customer-links",
            KeySchema=[{"AttributeName": "api_key_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "api_key_id", "AttributeType": "S"},
                {"AttributeName": "stripe_customer_id", "AttributeType": "S"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "gsi_stripe_customer",
                    "Keys": [{"AttributeName": "stripe_customer_id", "KeyType": "HASH"}],
                    "Projection": {"ProjectionType": "ALL"},
                    "ProvisionedThroughput": {"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
                }
            ],
            BillingMode="PAY_PER_REQUEST",
        )

        # Wait for table to be active
        table.wait_until_exists()
        yield table


@pytest.fixture
def api_gateway_usage_response():
    """API Gateway usage response fixture."""
    return {
        "usagePlanId": "METERED",
        "startDate": "2024-01-01",
        "endDate": "2024-01-31",
        "items": {"key_metered789": [[10, 1000], [11, 1500], [12, 2000]]},
    }


# Legacy fixtures for backward compatibility
@pytest.fixture
def fakes():
    """Legacy fixture for existing tests."""
    repo = MockCustomerRepo()
    repo.save(
        Link(
            api_key_id="k1",
            stripe_customer_id="cus_1",
            plan="metered",
            metered_subscription_item_id="si_123",
        )
    )
    repo.save(Link(api_key_id="k2", stripe_customer_id="cus_1", plan="free"))
    return repo, MockStripePort(), MockUsageSource(42)
