from collections.abc import Awaitable, Callable
from pathlib import Path

from pydantic_ai import Agent, RunContext
from pydantic_ai.agent import EventStreamHandler
from pydantic_ai.models import Model
from pydantic_ai_backends import LocalBackend
from pydantic_ai_backends.permissions.types import (
    OperationPermissions,
    PermissionRule,
    PermissionRuleset,
)
from pydantic_ai_summarization import create_sliding_window_processor

from app.context import Context
from app.context.state import State
from app.models import LoggingModel, build_model, build_smart_model
from app.tools import (
    configure_console_toolset,
    configure_get_text_tool,
    configure_send_file_tool,
    configure_skill_creation_toolset,
    configure_skill_usage_toolset,
    configure_web_fetch_tool,
)

from .deps import Deps
from .history import load_history, save_history


def create_chat_agent(
    ctx: Context,
    save_requests_dir: Path | None = None,
    model: Model | None = None,
) -> Agent[Deps, str]:
    settings = ctx.settings.get()
    instructions = ctx.prompt.get().content
    tools = ctx.tools.get().model_dump()
    enable_execute = ctx.tools.get().enable_execute

    if model is None:
        model = build_model(settings)
    if save_requests_dir is not None:
        model = LoggingModel(model, save_requests_dir)

    sliding_window = create_sliding_window_processor(
        trigger=("tokens", 256_000),
        keep=("tokens", 128_000),
    )

    agent: Agent[Deps, str] = Agent(
        model,
        deps_type=Deps,
        retries=2,
        history_processors=[sliding_window],
    )

    @agent.instructions
    async def _agent_instructions(_ctx: RunContext[Deps]) -> str:
        return instructions

    if tools["console"]:
        agent = configure_console_toolset(agent, enable_execute=enable_execute)
    if tools["get_text"]:
        agent = configure_get_text_tool(agent)
    if tools["send_file"]:
        agent = configure_send_file_tool(agent)
    if tools["skill_usage"]:
        agent = configure_skill_usage_toolset(agent, skills_dir=ctx.paths.skills_path)
    if tools["skill_creation"]:
        smart_model = build_smart_model(settings)
        agent = configure_skill_creation_toolset(agent, model=smart_model)
    if tools["web_fetch"]:
        agent = configure_web_fetch_tool(agent)

    return agent


def create_reactive_chat_agent(
    ctx: Context, save_requests_dir: Path | None = None
) -> State[Agent[Deps, str]]:
    return State(
        factory_fn=lambda: create_chat_agent(ctx, save_requests_dir),
        dependencies=[ctx.models, ctx.tools, ctx.prompt],
    )


def build_dependencies(
    ctx: Context,
    user_id: str | int,
    ask_user: Callable[[str, list[dict[str, str]]], Awaitable[str]] | None = None,
    send_file: Callable[[str, bytes], Awaitable[None]] | None = None,
) -> Deps:
    workspace_dir = ctx.paths.user_workspace(user_id)
    skills_dir = ctx.paths.user_skills_path(user_id)

    deny_skills = OperationPermissions(
        default="allow",
        rules=[
            PermissionRule(
                pattern=f"{skills_dir}/**",
                action="deny",
                description="A pasta de skills é gerenciada pelas ferramentas de skill.",
            ),
        ],
    )
    backend = LocalBackend(
        root_dir=workspace_dir,
        allowed_directories=[str(workspace_dir)],
        enable_execute=ctx.tools.get().enable_execute,
        ask_fallback="deny",
        permissions=PermissionRuleset(write=deny_skills, edit=deny_skills),
    )

    return Deps(
        backend=backend,
        workspace_dir=workspace_dir,
        skills_dir=skills_dir,
        ask_user=ask_user,
        send_file=send_file,
    )


async def run_agent_interaction(
    ctx: Context,
    agent: Agent[Deps, str],
    user_input: str,
    user_id: str | int,
    event_stream_handler: EventStreamHandler[Deps] | None = None,
    ask_user: Callable[[str, list[dict[str, str]]], Awaitable[str]] | None = None,
    send_file: Callable[[str, bytes], Awaitable[None]] | None = None,
) -> str:
    history_path = ctx.paths.user_history_path(user_id)

    deps = build_dependencies(
        ctx=ctx,
        user_id=user_id,
        ask_user=ask_user,
        send_file=send_file,
    )

    history = load_history(history_path)

    response = await agent.run(
        user_input,
        deps=deps,
        message_history=history,
        event_stream_handler=event_stream_handler,
    )

    save_history(history_path, response.all_messages())

    return response.output
