# -*- encoding: utf-8 -*-

"""
Auto Blogger - AI Requests / Response Data Models to Request Handles

Defines types :mod:`dataclasses` for all data flowing in and out of
the AI generation layer. Using :mod:`dataclasses` keeps the models
lightweight and framework-agnostic while still providing full type
hint coverage across any LLM agents.

These models are intentionally immutable (``frozen = True``). If a
response needs to be augmented, then create a new instance.
"""

import datetime as dt

from typing import Optional
from dataclasses import dataclass, field

from autoblogs.config.constants import AIProvider

@dataclass(frozen = True)
class AIModel:
    """
    Configuration of AI Model, Provider and Configuration

    The AI model configuration that globally defines the model to used,
    provider of the model and other general configuration settings.

    :type  provider: AIProvider
    :param provider: Name of the supported AI/LLM Agents provider. The
        value can be any of the defined :class:`AIProvider` values,
        defaults to ``AIProvider.LOCAL`` value.

    :type  useModel: Optional[Union[ClaudeModel, OpenAIModel, Any]]
    :param useModel: Name of the model, this can be any supported model
        that is widely available. The name of the model can be any
        of the ``Enum`` as per the base model or any value. In the
        forward integration model this parameter should be controlled.

    :type  max_tokens: int
    :param max_tokens: Maximum number of tokens the model can generate
        in its response. It does **not** limit the total tokens for
        the request - which is the total sum of input (prompt, system
        messages, retreived data) and output tokens (completion) and
        must stay within the model's context limit. Default to 4096.

    :type  temperature: float
    :param temperature: A crucial settings in a large language models
        (LLMs) that controls the randomness and creativity of the
        model's output. A lower temperature makes the model more
        deterministic. Conversely, a higher temperature introduces
        more variability, but a very large value can produce less
        coherent outputs. Typically a value of ``0.70`` (default) is
        generally considered as a good starting point.
    """

    provider : AIProvider = AIProvider.LOCAL
    useModel : Optional[str] = None

    # general model configuration parameters, usage as per model
    max_tokens  : int = 4096
    temperature : float = 0.7


@dataclass(frozen = True)
class AIRequest:
    """
    Input Parameters for a Single AI Generation Output Call

    The request class handles a single generation call and provides
    handlers to format the response for any framework. The class
    carries everything a client needs to construct and send a prompt,
    including optional prior-draft context for refinement rounds.

    :type  topic: str
    :param topic: Topic name for the content/blog post/etc. which can
        typically be the redirect URL or page name.

    :type  prompt: str
    :param prompt: Detailed prompt required to generate the content,
        this can be a detailed string and related context can be
        added for finer controls.

    :type  context: str
    :param context: The context is the additional requirement that is
        passed to the model to make a cleaner and refined structure.
        Check the pre-defined :mod:`jinja2` templates that can be used
        to format the context or define your own.
    """

    topic   : str
    prompt  : str
    context : Optional[str] = None

    # request releated and content tagging/reviewer related details
    request_id : Optional[str] = None
    created_by : Optional[str] = None
    reviewed_by : Optional[str] = None

    created_at : dt.date = field(
        default_factory = lambda : dt.datetime.now().date()
    )


@dataclass(frozen = True)
class AIResponse:
    """
    Output Parameters for a Single AI Response Call

    The class handles the response from a single AI call that can
    be used to generate the content.

    :type  request_id: str
    :param request_id: Request ID that can be either generated by
        the LLM agent or can be database internals.

    :type  raw_response: str
    :param raw_response: Complete raw response generated by the LLM
        agent. Use the raw response to format the underlying output.

    :type  in_tokens: int
    :param in_tokens: Total input tokens (approximate) used by the
        model to generate the response.

    :type  out_tokens: int
    :param out_tokens: Total output tokens (approximate) used by the
        model to generate the response.

    :type  latency: float
    :param latency: The amount of time taken by the LLM agent to
        generate the output from the prompt. This can also include the
        network latency (if any).
    """

    request_id   : str
    raw_response : Optional[str]

    # ? get the tokens, can be used to optimize the model
    in_tokens  : int
    out_tokens : int

    # ? latency can be termed as the time b/w input and output
    latency : float

    created_at : dt.date = field(
        default_factory = lambda : dt.datetime.now().date()
    )


    @property
    def total_tokens(self) -> int:
        """
        Return the Approximate Number of Tokens Used
        """

        return self.in_tokens + self.out_tokens


    @property
    def word_count(self) -> int:
        """
        Return the Approximate Number of Words in Raw Text
        """

        return len(str(self.raw_response).split())
