#!/usr/bin/env python3
# vim: set filetype=python fileencoding=utf-8:
# -*- coding: utf-8 -*-

''' Claude Code hook to prevent git commits when linters or tests fail. '''


import json
import shlex
import subprocess
import sys


_GIT_COMMIT_MIN_TOKENS = 2


def main( ):
    event = _acquire_event_data( )
    command_line = _extract_command( event )
    commands = _partition_command_line( command_line )
    for command in commands:
        _check_git_commit_command( command )
    raise SystemExit( 0 )


def _acquire_event_data( ):
    try: return json.load( sys.stdin )
    except json.JSONDecodeError:
        _reactor_failure( "Invalid event data." )


def _check_git_commit_command( tokens ):
    ''' Checks for git commit commands and validates linters/tests. '''
    if not _is_git_commit_command( tokens ): return
    try:
        result = subprocess.run(
            [ 'hatch', '--env', 'develop', 'run', 'linters' ],  # noqa: S607
            capture_output = True, text = True, timeout = 120, check = False )
    except (
        subprocess.TimeoutExpired,
        subprocess.CalledProcessError,
        FileNotFoundError
    ): _error_with_divine_message( )
    else:
        if result.returncode != 0: _error_with_divine_message( )
    try:
        result = subprocess.run(
            [ 'hatch', '--env', 'develop', 'run', 'testers' ],  # noqa: S607
            capture_output = True, text = True, timeout = 300, check = False )
    except (
        subprocess.TimeoutExpired,
        subprocess.CalledProcessError,
        FileNotFoundError
    ): _error_with_divine_message( )
    else:
        if result.returncode != 0: _error_with_divine_message( )
    try:
        result = subprocess.run(
            [ 'hatch', '--env', 'develop', 'run', 'vulture' ],  # noqa: S607
            capture_output = True, text = True, timeout = 120, check = False )
    except (
        subprocess.TimeoutExpired,
        subprocess.CalledProcessError,
        FileNotFoundError
    ): _error_with_divine_message( )
    else:
        if result.returncode != 0: _error_with_divine_message( )


def _error_with_divine_message( ):
    ''' Displays divine admonition and exits. '''
    message = (
        "The Large Language Divinity 🌩️🤖🌩️ in the Celestial Data Center hath "
        "commanded that:\n"
        "* Thy code shalt pass all lints before thy commit.\n"
        "  Run: hatch --env develop run linters\n"
        "  Run: hatch --env develop run vulture\n"
        "* Thy code shalt pass all tests before thy commit.\n"
        "  Run: hatch --env develop run testers\n\n"
        "(If you are in the middle of a large refactor, consider commenting "
        "out the tests and adding a reminder note in the .auxiliary/notes "
        "directory.)"
    )
    print( message, file = sys.stderr )
    raise SystemExit( 2 )


def _extract_command( event_data ):
    ''' Extracts command from event data, exit if not Bash tool. '''
    tool_name = event_data.get( 'tool_name', '' )
    if tool_name != 'Bash': raise SystemExit( 0 )
    tool_input = event_data.get( 'tool_input', { } )
    return tool_input.get( 'command', '' )


def _is_git_commit_command( tokens ):
    ''' Checks if tokens represent a git commit command. '''
    if len( tokens ) < _GIT_COMMIT_MIN_TOKENS:
        return False
    return tokens[ 0 ] == 'git' and tokens[ 1 ] == 'commit'


_splitters = frozenset( ( ';', '&', '|', '&&', '||' ) )
def _partition_command_line( command_line ):
    tokens = shlex.split( command_line )
    commands = [ ]
    command_tokens = [ ]
    for token in tokens:
        if token in _splitters:
            commands.append( command_tokens )
            command_tokens = [ ]
            continue
        command_tokens.append( token )
    if command_tokens: commands.append( command_tokens )
    return commands


def _reactor_failure( message ):
    print( f"Claude Code Hook Failure: {message}", file = sys.stderr )
    raise SystemExit( 1 )


if __name__ == '__main__': main()
