from __future__ import annotations

from datetime import datetime
from enum import StrEnum

from pydantic import BaseModel

from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType
from skyvern.webeye.actions.actions import ActionType
from skyvern.webeye.actions.models import AgentStepOutput


class StepStatus(StrEnum):
    created = "created"
    running = "running"
    failed = "failed"
    completed = "completed"

    def can_update_to(self, new_status: StepStatus) -> bool:
        allowed_transitions: dict[StepStatus, set[StepStatus]] = {
            StepStatus.created: {StepStatus.running},
            StepStatus.running: {StepStatus.completed, StepStatus.failed},
            StepStatus.failed: set(),
            StepStatus.completed: set(),
        }
        return new_status in allowed_transitions[self]

    def requires_output(self) -> bool:
        status_requires_output = {StepStatus.completed}
        return self in status_requires_output

    def cant_have_output(self) -> bool:
        status_cant_have_output = {StepStatus.created, StepStatus.running}
        return self in status_cant_have_output

    def is_terminal(self) -> bool:
        status_is_terminal = {StepStatus.failed, StepStatus.completed}
        return self in status_is_terminal


class Step(BaseModel):
    created_at: datetime
    modified_at: datetime
    task_id: str
    step_id: str
    status: StepStatus
    output: AgentStepOutput | None = None
    order: int
    is_last: bool
    retry_index: int = 0
    organization_id: str | None = None
    input_token_count: int = 0
    output_token_count: int = 0
    step_cost: float = 0

    def validate_update(self, status: StepStatus | None, output: AgentStepOutput | None, is_last: bool | None) -> None:
        old_status = self.status

        if status and not old_status.can_update_to(status):
            raise ValueError(f"invalid_status_transition({old_status},{status},{self.step_id})")

        if status and status.requires_output() and output is None:
            raise ValueError(f"status_requires_output({status},{self.step_id})")

        if status and status.cant_have_output() and output is not None:
            raise ValueError(f"status_cant_have_output({status},{self.step_id})")

        if output is not None and status is None:
            raise ValueError(f"cant_set_output_without_updating_status({self.step_id})")

        if self.output is not None and output is not None:
            raise ValueError(f"cant_override_output({self.step_id})")

        if is_last and not self.status.is_terminal():
            raise ValueError(f"is_last_but_status_not_terminal({self.status},{self.step_id})")

        if is_last is False:
            raise ValueError(f"cant_set_is_last_to_false({self.step_id})")

    def is_goal_achieved(self) -> bool:
        if self.status != StepStatus.completed:
            return False
        # TODO (kerem): Remove this check once we have backfilled all the steps
        if self.output is None or self.output.actions_and_results is None:
            return False

        # Check if there is a successful complete action
        for action, action_results in self.output.actions_and_results:
            if action.action_type != ActionType.COMPLETE:
                continue

            if any(action_result.success for action_result in action_results):
                return True

        return False

    def is_terminated(self) -> bool:
        if self.status != StepStatus.completed:
            return False
        # TODO (kerem): Remove this check once we have backfilled all the steps
        if self.output is None or self.output.actions_and_results is None:
            return False

        # Check if there is a successful terminate action
        for action, action_results in self.output.actions_and_results:
            if action.action_type != ActionType.TERMINATE:
                continue

            if any(action_result.success for action_result in action_results):
                return True

        return False


class Organization(BaseModel):
    organization_id: str
    organization_name: str
    webhook_callback_url: str | None = None
    max_steps_per_run: int | None = None

    created_at: datetime
    modified_at: datetime


class OrganizationAuthToken(BaseModel):
    id: str
    organization_id: str
    token_type: OrganizationAuthTokenType
    token: str
    valid: bool
    created_at: datetime
    modified_at: datetime


class TokenPayload(BaseModel):
    sub: str
    exp: int
