#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.8"
# dependencies = [
#     "requests",
# ]
# ///
"""
A command-line tool to upload files or stdin content to a paste service.

Examples:
    # Upload a file (syntax auto-detected)
    ./pasted my_file.py

    # Upload stdin content
    echo "Hello World" | ./pasted

    # Upload with notifications and raw link
    ./pasted --notify --raw my_file.py

    # Upload with custom syntax and expiration
    ./pasted --syntax python --expiration 3600 my_file.py

    # Upload as public paste (syntax still auto-detected)
    ./pasted --exposure public my_file.py

    # Run with uvx
    uvx /path/to/pasted my_file.py

Note: Syntax is automatically detected based on file content when not explicitly specified.
"""

import argparse
import enum
import logging
import re
import subprocess
import sys

from pathlib import Path
from typing import Dict, Optional

import requests


class Result(enum.IntEnum):
    """API response codes from the paste service."""
    Success = enum.auto()
    InvalidData = enum.auto()
    InvalidSyntax = enum.auto()
    InvalidExpiration = enum.auto()
    InvalidExposure = enum.auto()
    InvalidToken = enum.auto()
    Throttled = enum.auto()


LINK = "https://paste.diath.net"
ENDPOINT = "https://paste.diath.net/api"

RESULT_MESSAGES: Dict[Result, str] = {
    Result.Success: "Paste added successfully.",
    Result.InvalidData: "Invalid form data supplied.",
    Result.InvalidSyntax: "Submitted syntax is not supported.",
    Result.InvalidExpiration: "Invalid expiration range specified.",
    Result.InvalidExposure: "Invalid exposure value specified.",
    Result.InvalidToken: "Invalid API token provided.",
    Result.Throttled: "You have been temporarily throttled by the system.",
}


class PasteError(Exception):
    """Custom exception for paste-related errors."""
    pass


def copy_to_clipboard(text: str) -> None:
    """Copy text to clipboard using xclip."""
    import os

    # Skip silently if no display environment
    if not os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY"):
        return

    try:
        # Try the exact same command that works in terminal
        proc = subprocess.Popen(
            ["xclip", "-selection", "clipboard"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            env=os.environ.copy()
        )

        # Send input and close stdin immediately
        proc.stdin.write(text)
        proc.stdin.close()

        # Wait for process to complete with timeout
        try:
            stdout, stderr = proc.communicate(timeout=3)
        except subprocess.TimeoutExpired:
            proc.kill()
            proc.communicate()
            logging.debug("xclip timed out and was killed")
            return

        if proc.returncode != 0:
            logging.debug(f"xclip failed: {stderr}")

    except (FileNotFoundError, OSError) as e:
        logging.debug(f"Clipboard operation failed: {e}")
    except Exception as e:
        logging.debug(f"Unexpected clipboard error: {e}")


def send_notification(title: str, message: str, icon: str = "emblem-default") -> None:
    """Send a desktop notification using notify-send."""
    try:
        subprocess.run(
            ["notify-send", "-i", icon, title, message],
            check=True,
            capture_output=True,
            timeout=1
        )
    except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e:
        logging.warning(f"Failed to send notification: {e}")


def read_file_content(filepath: Path) -> str:
    """Read content from a file."""
    try:
        return filepath.read_text(encoding="utf-8")
    except FileNotFoundError:
        raise PasteError(f"File doesn't exist: {filepath}")
    except PermissionError:
        raise PasteError(f"Permission denied: {filepath}")
    except UnicodeDecodeError:
        raise PasteError(f"Unable to decode file as UTF-8: {filepath}")


def read_stdin_content() -> str:
    """Read content from stdin."""
    if sys.stdin.isatty():
        raise PasteError("No input provided via stdin")
    return sys.stdin.read()


def create_parser() -> argparse.ArgumentParser:
    """Create and configure the argument parser."""
    parser = argparse.ArgumentParser(
        description=f"Upload a local file or stdin contents to {LINK}."
    )
    parser.add_argument(
        "--notify",
        action="store_true",
        default=False,
        help="Display notification."
    )
    parser.add_argument(
        "--expiration",
        type=int,
        default=0,
        help="Set paste expiration."
    )
    parser.add_argument(
        "--exposure",
        default="private",
        choices=("public", "private"),
        help="Set paste exposure.",
    )
    parser.add_argument(
        "--syntax",
        default="None",
        help="Set paste syntax. If not specified or set to 'None', will attempt to auto-detect based on content."
    )
    parser.add_argument(
        "filepath",
        nargs="?",
        type=Path,
        help="The path to the file that you want to upload."
    )

    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "--raw",
        action="store_true",
        default=False,
        help="Return a link to a raw version of the entry.",
    )
    group.add_argument(
        "--simple",
        action="store_true",
        default=False,
        help="Return a link to a simple version of the entry.",
    )

    return parser


def get_api_token() -> str:
    """Get API token from environment variable."""
    import os
    token = os.environ.get("PASTE_D_TOKEN")
    if not token:
        raise PasteError(
            "This script requires an API token in the PASTE_D_TOKEN environment variable."
        )
    return token


def detect_syntax(content: str) -> Optional[str]:
    """Detect the syntax/language of the given content."""
    if not content or len(content.strip()) < 10:
        return None

    # Programming languages (high priority)
    programming_languages = {
        "Python": [
            re.compile(r"def\s+\w+\s*\([^)]*\):"),
            re.compile(r"import\s+\w+"),
            re.compile(r"from\s+\w+\s+import"),
            re.compile(r"if\s+__name__\s*==\s*['\"]__main__['\"]:")
        ],
        "JavaScript": [
            re.compile(r"\b(function|let|const|=>)\b"),
            re.compile(r"console\.log\s*\("),
            re.compile(r"require\s*\("),
            re.compile(r"import.*from")
        ],
        "TypeScript": [
            re.compile(r":\s*(string|number|boolean|any|void)\b"),
            re.compile(r"interface\s+\w+"),
            re.compile(r"type\s+\w+\s*=")
        ],
        "Java": [
            re.compile(r"public\s+class\s+\w+"),
            re.compile(r"public\s+static\s+void\s+main"),
            re.compile(r"System\.out\.print"),
            re.compile(r"package\s+[\w.]+;")
        ],
        "C++": [
            re.compile(r"#include\s*<iostream>"),
            re.compile(r"using\s+namespace\s+std"),
            re.compile(r"std::"),
            re.compile(r"\b(class|template|new|delete)\b")
        ],
        "C": [
            re.compile(r"#include\s*<stdio\.h>"),
            re.compile(r"int\s+main\s*\("),
            re.compile(r"printf\s*\("),
            re.compile(r"\b(malloc|free)\b")
        ],
        "Go": [
            re.compile(r"package\s+main"),
            re.compile(r"func\s+main\s*\("),
            re.compile(r"fmt\.Print"),
            re.compile(r"import\s*\(")
        ],
        "Rust": [
            re.compile(r"fn\s+main\s*\("),
            re.compile(r"fn\s+\w+"),
            re.compile(r"println!\s*\("),
            re.compile(r"let\s+mut\s+")
        ],
        "PHP": [
            re.compile(r"<\?php"),
            re.compile(r"\$\w+"),
            re.compile(r"function\s+\w+"),
            re.compile(r"echo\s")
        ],
        "Bash": [
            re.compile(r"^#!/"),
            re.compile(r"\$\w+"),
            re.compile(r"echo\s"),
            re.compile(r"\[\s*.*\s*\]")
        ],
        "Lua": [
            re.compile(r"function\s+\w+"),
            re.compile(r"local\s+\w+"),
            re.compile(r"print\s*\("),
            re.compile(r"end\b")
        ],
        "SQL": [
            re.compile(r"\bSELECT\b.*\bFROM\b", re.IGNORECASE),
            re.compile(r"\bINSERT\s+INTO\b", re.IGNORECASE),
            re.compile(r"\bCREATE\s+TABLE\b", re.IGNORECASE)
        ]
    }

    # Markup/config languages (lower priority, simple patterns only)
    markup_languages = {
        "HTML": [
            re.compile(r"<!DOCTYPE\s+html>", re.IGNORECASE),
            re.compile(r"<!html", re.IGNORECASE)
        ],
        "XML": [
            re.compile(r"<\?xml\s+version", re.IGNORECASE)
        ],
        "CSS": [
            re.compile(r"[.#]\w+\s*\{[^}]*\}")
        ],
        "JSON": [
            re.compile(r"^\s*\{\s*\"[^\"]*\"\s*:"),
            re.compile(r"^\s*\[\s*\{")
        ],
        "YAML": [
            re.compile(r"---"),
            re.compile(r"^\w+:\s*$", re.MULTILINE)
        ]
    }

    # First, try programming languages (priority)
    for language, patterns in programming_languages.items():
        matches = 0
        for pattern in patterns:
            if pattern.search(content):
                matches += 1

        if matches >= 2:
            return language

    # Then try markup languages (only with obvious markers)
    for language, patterns in markup_languages.items():
        for pattern in patterns:
            if pattern.search(content):
                return language

    return None


def prepare_request_data(args: argparse.Namespace, content: str) -> Dict[str, str]:
    """Prepare the request data for the API call."""
    syntax = args.syntax

    # Auto-detect syntax if not explicitly provided or set to "None"
    if syntax == "None":
        detected_syntax = detect_syntax(content)
        if detected_syntax:
            syntax = detected_syntax
            print(f"Auto-detected syntax: {syntax}")

    return {
        "token": get_api_token(),
        "exposure": "0" if args.exposure == "public" else "1",
        "expiration": str(args.expiration),
        "syntax": syntax,
        "content": content,
    }


def get_link_suffix(args: argparse.Namespace) -> str:
    """Get the appropriate link suffix based on arguments."""
    if args.raw:
        return "/raw"
    elif args.simple:
        return "/simple"
    return ""


def handle_successful_response(response_text: str, args: argparse.Namespace) -> None:
    """Handle a successful API response."""
    try:
        parts = response_text.split(":")
        if len(parts) < 2:
            raise ValueError("Invalid response format")

        code = int(parts[0])
        hash_value = parts[1]

        if code != Result.Success:
            error_msg = RESULT_MESSAGES.get(Result(code), "Unknown error")
            print(f"#{code}: {error_msg}")

            if args.notify:
                send_notification(
                    "Paste",
                    f"Invalid return code returned by the endpoint.\n{error_msg}",
                    "emblem-unreadable"
                )
            return

        # Success case
        link = f"{LINK}/{hash_value}{get_link_suffix(args)}"

        print(RESULT_MESSAGES[Result.Success])
        print(f"Hash: {hash_value}")
        print(f"Link: {link}")
        copy_to_clipboard(link)

        if args.notify:
            send_notification("Paste", "Paste has been added successfully.")

    except (ValueError, IndexError) as e:
        raise PasteError(f"Invalid response format: {e}")


def main() -> None:
    """Main entry point of the script."""
    logging.basicConfig(level=logging.WARNING)

    parser = create_parser()
    args = parser.parse_args()

    try:
        # Get content from file or stdin
        if args.filepath:
            content = read_file_content(args.filepath)
        else:
            content = read_stdin_content()

        # Prepare and send request
        data = prepare_request_data(args, content)

        response = requests.post(ENDPOINT, data=data, timeout=30)

        if response.status_code == requests.codes.ok:
            handle_successful_response(response.text, args)
        else:
            print(f"Invalid status code returned by the endpoint: #{response.status_code}")
            if args.notify:
                send_notification(
                    "Paste",
                    "Invalid status code returned by the endpoint.",
                    "emblem-unreadable"
                )

    except PasteError as e:
        print(f"Error: {e}")
        if args.notify:
            send_notification("Paste", f"Error: {e}", "emblem-unreadable")
        sys.exit(1)
    except requests.RequestException as e:
        print(f"Network error: {e}")
        if args.notify:
            send_notification("Paste", f"Network error: {e}", "emblem-unreadable")
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nOperation cancelled by user.")
        sys.exit(1)


if __name__ == "__main__":
    main()