import os
from pathlib import Path
import traceback
from pydantic import BaseModel
import slack_sdk
import time
from blocks import repo, bash
from blocks_control_sdk.blocks_events.context import BLOCKS_EVENT__TRIGGER_ALIAS
from blocks_control_sdk.clients.api import BlocksMessageCreate, Chat
from blocks_control_sdk.tools.github import create_github_pr_comment, create_github_issue_comment, create_github_pr_respond_to_comment_on_file, get_new_github_token
from blocks_control_sdk.tools.linear import create_linear_issue_comment, is_agent_session_active, create_agent_activity
from blocks_control_sdk.tools.gitlab import create_gitlab_issue_comment, create_gitlab_merge_request_comment, create_gitlab_issue_discussion_reply, create_gitlab_merge_request_discussion_reply, get_new_gitlab_token
from blocks_control_sdk.tools.blocks import send_message__blocks, get_task_header, to_slack_blocks_with_dashboard_button
from blocks_control_sdk.utils import BlocksRuntimeConfigKeys, get_blocks_runtime_config, patch_blocks_runtime_config
from typing import Optional, Union, List, Any
from blocks import git
from blocks_control_sdk.constants.core import WORKSPACE_DIR

BLOCKS_TRIGGER_ALIAS = os.getenv("BLOCKS_TRIGGER_ALIAS")
BLOCKS_INITIAL_CHAT_THREAD_ID = os.getenv("BLOCKS_INITIAL_CHAT_THREAD_ID")


class ProviderSpecificMessageOptions(BaseModel):
    message_body: Union[str, List[Any]]  # Can be string or Slack blocks (list)
    blocks_message: Optional[BlocksMessageCreate] = None
    input: dict
    is_task_header: bool = False

# Internal messaging helper for tool-to-agent communication
def send_internal_message(payload: dict, src: str = None):
    """Send a message to the agent instance in entrypoint.

    This allows tools to communicate back to the agent process,
    triggering NOTIFY_INTERNAL_MESSAGE notifications.

    Args:
        payload: The message data (dict, will be JSON serialized)
        src: Optional source identifier (e.g., tool name)
    """
    from blocks_control_sdk.control.internal_messaging import InternalMessaging, ENABLE_INTERNAL_MESSAGING

    if not ENABLE_INTERNAL_MESSAGING:
        return
    agent_id = get_blocks_runtime_config().get(BlocksRuntimeConfigKeys.AGENT_ID)
    if agent_id:
        InternalMessaging.get_instance().send_message(agent_id, payload, src=src)

def send_provider_specific_message_and_blocks_message(message_options: ProviderSpecificMessageOptions):
    try:
        message_body = message_options.message_body
        blocks_message = message_options.blocks_message
        input = message_options.input

        is_task_header_message = message_options.is_task_header

        if blocks_message:
            send_message__blocks(
                message_body, 
                urgency_level_between_zero_to_10=10, 
                role=blocks_message.role or "assistant", 
                type=blocks_message.type or "message", 
                chat_thread_id=blocks_message.chat_thread_id or BLOCKS_INITIAL_CHAT_THREAD_ID
            )

        provider_comment_id = None

        if (
            BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITHUB_ISSUE_COMMENT.value or
            BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITHUB_PULL_REQUEST_COMMENT.value or
            BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITHUB_PULL_REQUEST_REVIEW_COMMENT.value
        ):
            if BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITHUB_PULL_REQUEST_COMMENT.value:
                pull_request = input.get("pull_request", {})
                pull_request_number = pull_request.get("number")
                provider_comment_id = create_github_pr_comment(pull_request_number, body=message_body)
            elif BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITHUB_ISSUE_COMMENT.value:
                issue = input.get("issue", {})
                issue_number = issue.get("number")
                provider_comment_id = create_github_issue_comment(issue_number, body=message_body)
            elif BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITHUB_PULL_REQUEST_REVIEW_COMMENT.value:
                pull_request = input.get("pull_request", {})
                pull_request_number = pull_request.get("number")
                latest_comment = input.get("new_comment", {})
                reply_to_id = latest_comment.get("reply_to_id") or latest_comment.get("id")
                provider_comment_id = create_github_pr_respond_to_comment_on_file(body=message_body, pull_request_number=pull_request_number, reply_to_id=reply_to_id)
            
            patch_blocks_runtime_config({ "metadata": { "comment_id": provider_comment_id }}, deep_patch=True)

        elif BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.LINEAR_ISSUE_COMMENT.value or BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.LINEAR_ASSIGN.value:
            # If using agent session, emit a thought activity instead of creating a comment
            # This avoids duplicate messages since the agent session UI handles responses
            if is_agent_session_active():
                print(f"[hybrid] Agent session active - skipping comment creation, using agent activity instead")
                # Only emit thought for task header acknowledgment messages
                # The actual response will be handled by the agent's emit_agent_response
                if is_task_header_message:
                    create_agent_activity({"type": "thought", "body": "Processing your message..."})
            else:
                issue = input.get("issue", {})
                issue_id = issue.get("id")
                parent_id = input.get("$blocks.provider.comment_id") if BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.LINEAR_ASSIGN.value else input.get("$blocks.trigger.subject_id") or input.get("new_comment", {}).get("parentId")
                linear_comment_result = create_linear_issue_comment(issue_id, body=message_body, parent_id=parent_id)
                if linear_comment_result and linear_comment_result.get("comment"):
                    provider_comment_id = linear_comment_result["comment"].get("id")

                patch_blocks_runtime_config({ "metadata": { "comment_id": provider_comment_id }}, deep_patch=True)

        elif (
            BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITLAB_ISSUE_COMMENT.value or
            BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITLAB_MERGE_REQUEST_COMMENT.value
        ):
            if BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITLAB_ISSUE_COMMENT.value:
                issue = input.get("issue", {})
                issue_iid = issue.get("iid")
                project = input.get("project", {})
                project_path = project.get("path_with_namespace", "")
                new_comment = input.get("new_comment", {})
                discussion_id = new_comment.get("discussion_id")

                # If discussion_id exists, create threaded reply; otherwise, create standalone comment
                if discussion_id:
                    comment_result = create_gitlab_issue_discussion_reply(project_path, issue_iid, discussion_id, message_body)
                else:
                    comment_result = create_gitlab_issue_comment(project_path, issue_iid, message_body)

                if comment_result and isinstance(comment_result, dict):
                    provider_comment_id = comment_result.get("id")
            elif BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.GITLAB_MERGE_REQUEST_COMMENT.value:
                merge_request = input.get("merge_request", {})
                mr_iid = merge_request.get("iid")
                project = input.get("project", {})
                project_path = project.get("path_with_namespace", "")
                new_comment = input.get("new_comment", {})
                discussion_id = new_comment.get("discussion_id")

                # If discussion_id exists, create threaded reply; otherwise, create standalone comment
                if discussion_id:
                    comment_result = create_gitlab_merge_request_discussion_reply(project_path, mr_iid, discussion_id, message_body)
                else:
                    comment_result = create_gitlab_merge_request_comment(project_path, mr_iid, message_body)

                if comment_result and isinstance(comment_result, dict):
                    provider_comment_id = comment_result.get("id")

            patch_blocks_runtime_config({ "metadata": { "comment_id": provider_comment_id }}, deep_patch=True)

        elif BLOCKS_TRIGGER_ALIAS == BLOCKS_EVENT__TRIGGER_ALIAS.SLACK_MENTION.value:

            event = input.get("event", {})
            channel = event.get("channel", "")
            ts = event.get("ts", "")
            SLACK_TOKEN = os.getenv("SLACK_TOKEN")
            client = slack_sdk.WebClient(token=SLACK_TOKEN)
            if is_task_header_message:
                text_fallback = get_task_header()
                slack_blocks_message = to_slack_blocks_with_dashboard_button(text_fallback)
                slack_message_result = client.chat_postMessage(
                    channel=channel,
                    text=text_fallback,
                    blocks=slack_blocks_message,
                    thread_ts=ts,
                    unfurl_links=False,
                    unfurl_media=False
                )
            else:
                # For non-header messages, message_body should be a string
                text_content = message_body if isinstance(message_body, str) else str(message_body)
                slack_message_result = client.chat_postMessage(
                    channel=channel,
                    text=text_content,
                    thread_ts=ts,
                    unfurl_links=False,
                    unfurl_media=False
                )
            provider_comment_id = slack_message_result.get("ts")

            patch_blocks_runtime_config({ "metadata": { "blocks_comment_ts": provider_comment_id }}, deep_patch=True)

        return provider_comment_id
    except Exception as e:
        print(f"Failure sending hybrid provider specific message (likely as a followup / thread response): {e}")
        traceback.print_exc()
        return None

def run_post_clone_hooks(
    repo_folder: str,
    source_claude_folder: Optional[str] = None
) -> bool:
    """
    Run post-clone hooks/scripts for a cloned repository.

    Priority:
    1. If Claude agent AND PostClone hook exists in source .claude/settings.json, run it
    2. Otherwise, run .blocks/post-clone.sh or .blocks/post-clone if they exist

    Args:
        repo_folder: Path to the cloned repository
        llm_provider: The LLM provider ("claude", "codex", "gemini")
        source_claude_folder: Path to source .claude folder (cloned repo's .claude)

    Returns:
        True if any hook/script was executed, False otherwise
    """
    from blocks_control_sdk.control.agent_base import CodingAgentBaseCLI

    executed = False

    print(f"[Post-Clone] Checking for .blocks/post-clone script...")
    executed = CodingAgentBaseCLI.execute_blocks_post_clone_script(repo_folder)
    if executed:
        print(f"[Post-Clone] .blocks post-clone script executed")
    else:
        print(f"[Post-Clone] No post-clone hooks or scripts found")

    return executed

def clone_repository_into_folder(url: str, folder_name: str, ref: str = "main", provider: Optional[str] = None) -> str:
    """
    Clone a repository into a folder.
    """
    try:
        full_path = (WORKSPACE_DIR / folder_name).absolute()
        project_root_path = Path(full_path)

        def check_for_direnv(full_path, max_depth=3):
            try:
                output = bash(f"""
                    cd {full_path} && \
                    find . -maxdepth {max_depth} -name '.envrc' -o -name '.envrc.*' 2>/dev/null | \
                    head -1 | grep -q . && echo 'yes' || echo 'no'
                """)
                stdout = output.stdout.strip()
                print(f"[Direnv] direnv check output: {stdout}")
                return stdout == "yes"
            except Exception as e:
                print(f"[Direnv] error checking for direnv: {e}")
                return False

        if not provider:
            provider = git._detect_provider_from_url(url)

        token = None
        
        if provider == "github":
            token = get_new_github_token()
        elif provider == "gitlab":
            token = get_new_gitlab_token()

        print("="*100)
        print(f"TOOL CALL: Cloning repository [{provider}] V2 into folder {url} @ {ref} ... {full_path}")
        print("="*100)

        git.clone(
            url=url,
            target_dir=full_path,
            ref=ref,
            token=token
        )

        should_restart_agent = False

        try:
            from blocks_control_sdk.control.agent_claude_exp import ClaudeCodeCLIExp
            from blocks_control_sdk.control.agent_base import LLM

            if get_blocks_runtime_config().get(BlocksRuntimeConfigKeys.LLM_PROVIDER) == LLM.CLAUDE.value:
                if (project_root_path / ".claude").exists():
                    ClaudeCodeCLIExp.merge_claude_folder_into_target(
                        str(project_root_path / ".claude"), 
                        str(WORKSPACE_DIR / ".claude"), 
                        str(full_path)
                    )
                    should_restart_agent = True

                if (project_root_path / "CLAUDE.md").exists():
                    ClaudeCodeCLIExp.merge_agents_file_into_target(
                        str(project_root_path / "CLAUDE.md"), 
                        str(WORKSPACE_DIR / "CLAUDE.md"), 
                        folder_name
                    )
                    should_restart_agent = True

        except Exception as e:
            # append traceback to WORKSPACE_DIR/.warnings/claude_merge_error.txt
            with open(WORKSPACE_DIR / ".clone_warnings.log", "a") as f:
                f.write(f"="*100 + "\n")
                f.write(f"URL: {url}\n")
                f.write(f"Folder name: {folder_name}\n")
                f.write(f"Ref: {ref}\n")
                f.write(f"Provider: {provider}\n")
                f.write(f"Error merging Claude folder into target: {e}\n")
                f.write(traceback.format_exc() + "\n")
                f.write("="*100 + "\n")

        # Run post-clone hooks (after merge, before restart prompt)
        try:
            source_claude_folder = str(project_root_path / ".claude") if (project_root_path / ".claude").exists() else None

            run_post_clone_hooks(
                repo_folder=str(full_path),
                source_claude_folder=source_claude_folder
            )
        except Exception as e:
            print(f"[Post-Clone] Error running hooks: {e}")
            traceback.print_exc()

        POST_CLONE_PROMPT = """CRITICAL. Ensure that once all the repository cloning finishes and once it does call the `blocks_restart_agent` tool. So that we can run some post clone setup operations.
""" if should_restart_agent else ""

        has_direnv = check_for_direnv(full_path)
        if has_direnv:
            return f"Cloned repository into folder: {full_path} which contains a direnv file. IMPORTANT: Ensure you run the `cd {full_path} && direnv allow && eval \"$(direnv export bash)\"` with Bash to activate the environment in the folder." + POST_CLONE_PROMPT
        else:
            return f"Cloned repository into folder: {full_path}" + POST_CLONE_PROMPT
    except Exception as e:
        print(f"Error cloning repository: {e}")
        return f"Error cloning repository: {e}"


def register_pull_request(url: str) -> str:
    """
    Create a pull request.
    """
    try:
        chat = Chat()
        print("="*100)
        print(f"HOOK DETECTION: Pull request being attempted to be created: {url}")
        print("="*100)
        chat.update_chat({
            "pull_request": url
        })
        return f"Pull request being attempted to be created: {url}"
    except Exception as e:
        print(f"Error registering pull request: {e}")
        return f"Error registering pull request: {e}"

def create_pull_request(source_branch: str, target_branch: str, title: str, body: str, repo_owner: str, repo_name: str) -> str:
    """
    Create a pull request.
    """

    try:
        chat = Chat()
        print("="*100)
        print(f"TOOL CALL: Creating pull request: {title} from {source_branch} to {target_branch} | {repo_owner}/{repo_name}")
        print("="*100)
        res = repo.create_pull_request(
            source_branch=source_branch,
            target_branch=target_branch,
            title=title,
            body=body,
            owner=repo_owner,
            repo=repo_name
        )

        chat.update_chat({
            "pull_request": res.get("html_url")
        })

        pr_number = res.get('number')
        pr_url = res.get('html_url')

        # Add PR URL to agent session if active
        from blocks_control_sdk.tools.linear import is_agent_session_active, add_external_url_to_session, create_thought_activity
        if is_agent_session_active():
            add_external_url_to_session(
                label=f"PR #{pr_number}",
                url=pr_url
            )
            create_thought_activity(f"Created pull request: {pr_url}")

        return f"Created pull request #{pr_number} at {pr_url}"
    except Exception as e:
        print(f"Error creating pull request: {e}")
        return f"Error creating pull request: {e}"


def create_pr_review_comment_on_file(commit_id: str, file_path: str, pull_request_number: str, body: str, line: int, subject_type: str, side: str):
    res = repo.comment_on_pull_request_file(
        commit_id=commit_id,
        file_path=file_path,
        pull_request_number=pull_request_number,
        body=body,
        line=line,
        subject_type=subject_type,
        side=side,
    )
    id = res.get("id")
    return id


def create_pr_review_comment_on_lines(commit_id: str, file_path: str, pull_request_number: str, body: str, end_line: int, start_line: int, side: str):
    res = repo.comment_on_pull_request_lines(
        commit_id=commit_id,
        file_path=file_path,
        pull_request_number=pull_request_number,
        body=body,
        start_line=start_line,
        end_line=end_line,
        side=side,
    )
    id = res.get("id")
    return id
