"""
AnyCost payload builder for formatting billing data.

Converts Polars DataFrames to AnyCost-compatible JSON payloads with
proper batching to stay under CloudZero's 4MB payload limit.
"""

import json
from datetime import datetime
from typing import List, Dict, Any
import polars as pl


class PayloadBuilder:
    """
    Build AnyCost API payloads from processed DataFrames.

    Single Responsibility: Format and batch billing data for AnyCost API.
    """

    MAX_PAYLOAD_SIZE_MB = 4  # CloudZero limit

    def build_payloads(
        self,
        df: pl.DataFrame,
        month: str
    ) -> List[Dict[str, Any]]:
        """
        Build batched payloads for AnyCost API.

        Args:
            df: CBF-mapped DataFrame
            month: Month in YYYY-MM format

        Returns:
            List of payloads, each under 4MB.
            First payload has operation="replace_drop",
            subsequent payloads have operation="sum".
        """
        records = df.to_dicts()

        # Process records to merge resource/type into resource/id
        processed_records = [self._process_record(record) for record in records]

        batches = self._create_batches(processed_records)
        month_timestamp = self._format_month_timestamp(month)

        payloads = []
        for i, batch in enumerate(batches):
            operation = "replace_drop" if i == 0 else "sum"
            payload = {
                "month": month_timestamp,
                "operation": operation,
                "data": batch
            }
            payloads.append(payload)

        return payloads

    def _process_record(self, record: Dict[str, Any]) -> Dict[str, Any]:
        """
        Process record to merge resource/type into resource/id.

        If resource/type exists, prepend it to resource/id as {type}/{id}.
        Remove resource/type from the record as it shouldn't be sent as a separate field.

        Args:
            record: Billing record dictionary

        Returns:
            Processed record with merged resource/id
        """
        # Check if resource/type exists in the record
        resource_type = record.get("resource/type")
        resource_id = record.get("resource/id")

        if resource_type and resource_id:
            # Merge: {resource/type}/{resource/id}
            record["resource/id"] = f"{resource_type}/{resource_id}"

        # Remove resource/type from payload (not a valid CBF field for AnyCost API)
        if "resource/type" in record:
            del record["resource/type"]

        return record

    def _create_batches(self, records: List[Dict]) -> List[List[Dict]]:
        """
        Batch records to stay under 4MB limit.

        Args:
            records: List of billing record dictionaries

        Returns:
            List of batches, each under 4MB when serialized
        """
        batches = []
        current_batch = []
        current_size = 0
        max_size_bytes = self.MAX_PAYLOAD_SIZE_MB * 1024 * 1024

        for record in records:
            record_size = len(json.dumps(record).encode("utf-8"))

            if current_size + record_size >= max_size_bytes and current_batch:
                batches.append(current_batch)
                current_batch = [record]
                current_size = record_size
            else:
                current_batch.append(record)
                current_size += record_size

        if current_batch:
            batches.append(current_batch)

        return batches

    def _format_month_timestamp(self, month: str) -> str:
        """
        Convert YYYY-MM to ISO 8601 timestamp.

        Args:
            month: Month in YYYY-MM format (e.g., "2025-01")

        Returns:
            ISO 8601 timestamp (e.g., "2025-01-01T00:00:00+00:00")
        """
        try:
            dt = datetime.strptime(f"{month}-01", "%Y-%m-%d")
            return dt.strftime("%Y-%m-%dT%H:%M:%S+00:00")
        except ValueError as e:
            raise ValueError(
                f"Invalid month format: {month}. Expected YYYY-MM (e.g., '2025-01')"
            ) from e
