from typing import List, Optional
from urllib.parse import urlparse

from e2b.exceptions import InvalidArgumentException
from e2b.sandbox._git.types import GitBranches, GitFileStatus, GitStatus


def derive_repo_dir_from_url(url: str) -> Optional[str]:
    """
    Derive the default repository directory name from a Git URL.

    :param url: Git repository URL
    :return: Repository directory name, if it can be determined
    """
    parsed = urlparse(url)
    if parsed.scheme not in ("http", "https"):
        return None
    trimmed_path = parsed.path.rstrip("/")
    if not trimmed_path:
        return None
    last_segment = trimmed_path.split("/")[-1]
    if not last_segment:
        return None
    return last_segment[:-4] if last_segment.endswith(".git") else last_segment


def _parse_ahead_behind(segment: Optional[str]) -> tuple[int, int]:
    """
    Parse the ahead/behind segment from porcelain branch info.

    :param segment: Segment text like "ahead 2, behind 1"
    :return: Tuple of (ahead, behind)
    """
    if not segment:
        return 0, 0
    ahead = 0
    behind = 0
    if "ahead" in segment:
        try:
            ahead = int(segment.split("ahead")[1].split(",")[0].strip())
        except Exception:
            ahead = 0
    if "behind" in segment:
        try:
            behind = int(segment.split("behind")[1].split(",")[0].strip())
        except Exception:
            behind = 0
    return ahead, behind


def _normalize_branch_name(name: str) -> str:
    """
    Normalize branch names from porcelain branch output.

    :param name: Raw branch name section
    :return: Normalized branch name
    """
    if name.startswith("HEAD (detached at "):
        return name.replace("HEAD (detached at ", "").rstrip(")")
    return (
        name.replace("HEAD (no branch)", "HEAD")
        .replace("No commits yet on ", "")
        .replace("Initial commit on ", "")
    )


def _derive_status(index_status: str, working_status: str) -> str:
    """
    Derive a normalized status label from porcelain status characters.

    :param index_status: Index status character
    :param working_status: Working tree status character
    :return: Normalized status label
    """
    statuses = {index_status, working_status}
    if "U" in statuses:
        return "conflict"
    if "R" in statuses:
        return "renamed"
    if "C" in statuses:
        return "copied"
    if "D" in statuses:
        return "deleted"
    if "A" in statuses:
        return "added"
    if "M" in statuses:
        return "modified"
    if "T" in statuses:
        return "typechange"
    if "?" in statuses:
        return "untracked"
    return "unknown"


def parse_git_status(output: str) -> GitStatus:
    """
    Parse `git status --porcelain=1 -b` output into a structured object.

    :param output: Git status output
    :return: Parsed `GitStatus`
    """
    lines = [line.rstrip() for line in output.split("\n") if line.strip()]
    current_branch: Optional[str] = None
    upstream: Optional[str] = None
    ahead = 0
    behind = 0
    detached = False
    file_status: List[GitFileStatus] = []

    if not lines:
        return GitStatus(
            current_branch=current_branch,
            upstream=upstream,
            ahead=ahead,
            behind=behind,
            detached=detached,
            file_status=file_status,
        )

    branch_line = lines[0]
    if branch_line.startswith("## "):
        branch_info = branch_line[3:]
        ahead_start = branch_info.find(" [")
        branch_part = branch_info if ahead_start == -1 else branch_info[:ahead_start]
        ahead_part = None if ahead_start == -1 else branch_info[ahead_start + 2 : -1]
        normalized_branch = _normalize_branch_name(branch_part)
        raw_branch = branch_part
        is_detached = raw_branch.startswith("HEAD (detached at ") or (
            "detached" in raw_branch
        )

        if is_detached or normalized_branch.startswith("HEAD"):
            detached = True
        elif "..." in normalized_branch:
            branch, upstream_branch = normalized_branch.split("...")
            current_branch = branch or None
            upstream = upstream_branch or None
        else:
            current_branch = normalized_branch or None

        ahead, behind = _parse_ahead_behind(ahead_part)

    for line in lines[1:]:
        if line.startswith("?? "):
            name = line[3:]
            file_status.append(
                GitFileStatus(
                    name=name,
                    status="untracked",
                    index_status="?",
                    working_tree_status="?",
                    staged=False,
                )
            )
            continue

        if len(line) < 3:
            continue
        index_status = line[0]
        working_status = line[1]
        path = line[3:]
        renamed_from: Optional[str] = None
        name = path
        if " -> " in path:
            renamed_from, name = path.split(" -> ", 1)

        file_status.append(
            GitFileStatus(
                name=name,
                status=_derive_status(index_status, working_status),
                index_status=index_status,
                working_tree_status=working_status,
                staged=index_status not in (" ", "?"),
                renamed_from=renamed_from,
            )
        )

    return GitStatus(
        current_branch=current_branch,
        upstream=upstream,
        ahead=ahead,
        behind=behind,
        detached=detached,
        file_status=file_status,
    )


def parse_git_branches(output: str) -> GitBranches:
    """
    Parse `git branch --format=%(refname:short)\t%(HEAD)` output.

    :param output: Git branch output
    :return: Parsed `GitBranches`
    """
    branches: List[str] = []
    current_branch: Optional[str] = None

    lines = [line.strip() for line in output.split("\n") if line.strip()]
    for line in lines:
        parts = line.split("\t")
        name = parts[0]
        branches.append(name)
        if len(parts) > 1 and parts[1] == "*":
            current_branch = name

    return GitBranches(branches=branches, current_branch=current_branch)


def parse_remote_url(output: str, remote: str) -> str:
    """
    Parse a git remote URL output and validate it's present.

    :param output: Git remote get-url output
    :param remote: Remote name for the error message
    :return: Remote URL
    """
    url = output.strip()
    if not url:
        raise InvalidArgumentException(
            f'Remote "{remote}" URL not found in repository.'
        )
    return url
