#!/bin/bash
# modularity-lint.sh - Pre-commit hook to detect hardcoded values
# Blocks commits containing hardcoded values that should be referenced via config/env

set -euo pipefail

RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

VIOLATIONS=0
WARNINGS=0

# 커밋 대상 파일 목록 가져오기
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)

if [[ -z "$STAGED_FILES" ]]; then
    exit 0
fi

# 파일이 검사 제외 대상인지 확인
should_skip_file() {
    local file="$1"
    # config/ 디렉토리 제외 (단일 소스 자체)
    [[ "$file" == config/* ]] && return 0
    # hooks/ 디렉토리 제외 (모듈화 시행 도구 자체)
    [[ "$file" == hooks/* ]] && return 0
    # memory/specs/, memory/tasks/, memory/reports/, memory/events/ 제외 (문서)
    [[ "$file" == memory/specs/* ]] && return 0
    [[ "$file" == memory/tasks/* ]] && return 0
    [[ "$file" == memory/reports/* ]] && return 0
    [[ "$file" == memory/events/* ]] && return 0
    # .md 파일 제외 (문서)
    [[ "$file" == *.md ]] && return 0
    # .json, .yaml, .yml 파일 제외 (설정 파일)
    [[ "$file" == *.json ]] && return 0
    [[ "$file" == *.yaml ]] && return 0
    [[ "$file" == *.yml ]] && return 0
    return 1
}

# tests/ 디렉토리인지 확인 (WARNING만, 차단 안함)
is_test_file() {
    local file="$1"
    [[ "$file" == tests/* ]] && return 0
    return 1
}

# staged diff에서 특정 파일의 추가된 라인들을 추출
# git diff --cached 출력에서 해당 파일의 +로 시작하는 라인만 반환
get_staged_lines() {
    local target_file="$1"
    git diff --cached -- "$target_file" 2>/dev/null \
        | grep '^+' \
        | grep -v '^+++' \
        | sed 's/^+//'
}

# staged diff에서 특정 파일의 추가된 라인 번호와 내용을 추출
get_staged_lines_with_numbers() {
    local target_file="$1"
    local line_num=0
    local in_hunk=0
    local new_line=0

    git diff --cached -- "$target_file" 2>/dev/null | while IFS= read -r line; do
        # hunk 헤더 파싱: @@ -old_start,old_count +new_start,new_count @@
        if [[ "$line" =~ ^@@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@ ]]; then
            new_line="${BASH_REMATCH[2]}"
            in_hunk=1
            continue
        fi

        if [[ "$in_hunk" -eq 1 ]]; then
            case "$line" in
                ---*) continue ;;  # 파일 헤더
                +++*) continue ;;  # 파일 헤더
                -*)
                    # 삭제된 라인: new_line 증가 없음
                    ;;
                +*)
                    # 추가된 라인: 내용 출력 후 new_line 증가
                    echo "${new_line}:${line:1}"
                    (( new_line++ )) || true
                    ;;
                *)
                    # 컨텍스트 라인: new_line 증가
                    (( new_line++ )) || true
                    ;;
            esac
        fi
    done
}

# 라인이 주석인지 확인 (Python/bash # 주석)
is_comment_line() {
    local line="$1"
    # 앞뒤 공백 제거 후 #로 시작하는지 확인
    local trimmed="${line#"${line%%[![:space:]]*}"}"
    [[ "$trimmed" == \#* ]] && return 0
    return 1
}

# 라인에 noqa 지시어가 있는지 확인
has_noqa() {
    local line="$1"
    [[ "$line" == *"# noqa: modularity"* ]] && return 0
    return 1
}

# 파일의 staged 내용에 triple-quote docstring이 있는지 감지하고,
# 해당 라인이 docstring 내부인지 확인하기 위해 전체 파일 내용 기준으로 체크
# (단순화: staged diff 라인에서 triple quote 토글 추적)
check_in_docstring() {
    local file="$1"
    local target_line="$2"
    local in_docstring=0
    local current_line=0

    # 파일 전체를 읽어서 해당 라인까지 triple-quote 상태 추적
    if [[ ! -f "$file" ]]; then
        return 1
    fi

    while IFS= read -r line; do
        (( current_line++ )) || true
        # triple quote 토글 카운트
        local count
        count=$(echo "$line" | grep -o '"""' | wc -l || echo 0)
        local count2
        count2=$(echo "$line" | grep -o "'''" | wc -l || echo 0)
        local total=$(( count + count2 ))
        if (( total % 2 != 0 )); then
            in_docstring=$(( 1 - in_docstring ))
        fi
        if (( current_line == target_line )); then
            if [[ "$in_docstring" -eq 1 ]]; then
                return 0
            else
                return 1
            fi
        fi
    done < "$file"
    return 1
}

# 파일에 특정 import가 있는지 확인 (staged + 기존)
file_has_import() {
    local file="$1"
    local import_name="$2"
    # 기존 파일에서 확인
    if [[ -f "$file" ]]; then
        if grep -q "from.*${import_name}\|import ${import_name}" "$file" 2>/dev/null; then
            return 0
        fi
    fi
    # staged 변경사항에서도 확인
    if git diff --cached -- "$file" 2>/dev/null | grep '^+' | grep -v '^+++' | grep -q "from.*${import_name}\|import ${import_name}"; then
        return 0
    fi
    return 1
}

# 파일에 dq_rules import가 있는지 확인 (staged + 기존) - 하위호환을 위해 유지
file_has_dq_rules_import() {
    local file="$1"
    file_has_import "$file" "dq_rules"
}

# 위반 또는 경고 출력
report_issue() {
    local is_warning="$1"
    local file="$2"
    local line_num="$3"
    local violation_msg="$4"
    local fix_msg="$5"

    if [[ "$is_warning" -eq 1 ]]; then
        echo -e "${YELLOW}⚠️  모듈화 경고 (tests/ fixture)${NC}"
    else
        echo -e "${RED}❌ 모듈화 위반 감지!${NC}"
    fi
    echo "파일: ${file}:${line_num}"
    echo "위반: ${violation_msg}"
    echo "수정: ${fix_msg}"
    echo ""
}

# ── JSON 기반 패턴 로딩 ──
# JSON 파일 경로 결정
_JSON_CONFIG_PATH="$(git rev-parse --show-toplevel 2>/dev/null || echo "${WORKSPACE_ROOT:-}")/config/lint-patterns.json"

# JSON 패턴 로딩 (jq 및 파일 존재 시)
JSON_PATTERNS_LOADED=0
declare -a PAT_NAMES=()
declare -a PAT_REGEXES=()
declare -a PAT_SEVERITIES=()
declare -a PAT_MESSAGES=()
declare -a PAT_FILE_TYPES=()
declare -a PAT_SKIP_IF_IMPORTS=()

if command -v jq >/dev/null 2>&1 && [[ -f "$_JSON_CONFIG_PATH" ]]; then
    _pat_count=$(jq '.patterns | length' "$_JSON_CONFIG_PATH" 2>/dev/null || echo "0")
    if [[ "$_pat_count" -gt 0 ]]; then
        for (( _i=0; _i<_pat_count; _i++ )); do
            PAT_NAMES+=( "$(jq -r ".patterns[$_i].name" "$_JSON_CONFIG_PATH")" )
            PAT_REGEXES+=( "$(jq -r ".patterns[$_i].regex" "$_JSON_CONFIG_PATH")" )
            PAT_SEVERITIES+=( "$(jq -r ".patterns[$_i].severity" "$_JSON_CONFIG_PATH")" )
            PAT_MESSAGES+=( "$(jq -r ".patterns[$_i].message" "$_JSON_CONFIG_PATH")" )
            # file_types 배열을 쉼표로 이어붙임 (예: "all" 또는 "py,sh")
            PAT_FILE_TYPES+=( "$(jq -r ".patterns[$_i].file_types | join(\",\")" "$_JSON_CONFIG_PATH")" )
            # skip_if_import: 없으면 빈 문자열
            PAT_SKIP_IF_IMPORTS+=( "$(jq -r ".patterns[$_i].skip_if_import // empty" "$_JSON_CONFIG_PATH")" )
        done
        JSON_PATTERNS_LOADED=1
    fi
fi

if [[ "$JSON_PATTERNS_LOADED" -eq 0 ]]; then
    # jq 없거나 JSON 파일 없을 때 경고 후 하드코딩 fallback
    if ! command -v jq >/dev/null 2>&1; then
        echo "⚠️  [modularity-lint] jq 미설치: 하드코딩 패턴으로 fallback합니다." >&2
    elif [[ ! -f "$_JSON_CONFIG_PATH" ]]; then
        echo "⚠️  [modularity-lint] lint-patterns.json 없음 ($_JSON_CONFIG_PATH): 하드코딩 패턴으로 fallback합니다." >&2
    else
        echo "⚠️  [modularity-lint] lint-patterns.json 파싱 실패: 하드코딩 패턴으로 fallback합니다." >&2
    fi

    PAT_NAMES=( "chat_id" "workspace_path" "font_size_84" "team_name" )
    PAT_REGEXES=( "6937032012" "/home/jay/workspace" '\b(84px|64px|40px)\b' '\bdev[1-8]-team\b' )
    PAT_SEVERITIES=( "FAIL" "FAIL" "FAIL" "WARN" )
    PAT_MESSAGES=(
        'config.constants.chat_id 사용'
        'config.paths 사용'
        'from tools.dq_rules import DQ_RULES 사용'
        'config.constants.teams 사용'
    )
    PAT_FILE_TYPES=( "all" "all" "py" "all" )
    PAT_SKIP_IF_IMPORTS=( "" "" "dq_rules" "" )
fi

# 패턴 수
PAT_COUNT=${#PAT_NAMES[@]}

# 메인 검사 루프
while IFS= read -r file; do
    # 파일 존재 확인
    [[ -z "$file" ]] && continue

    # 제외 대상 파일 스킵
    if should_skip_file "$file"; then
        continue
    fi

    # tests/ 파일 여부
    IS_TEST=0
    if is_test_file "$file"; then
        IS_TEST=1
    fi

    # 파일 확장자 추출 (점 없이, 예: py sh js)
    FILE_EXT="${file##*.}"

    # 각 패턴의 skip_if_import 사전 계산 (파일별 1회)
    declare -a PAT_SKIP=()
    for (( pi=0; pi<PAT_COUNT; pi++ )); do
        skip_import="${PAT_SKIP_IF_IMPORTS[$pi]}"
        if [[ -n "$skip_import" ]]; then
            if file_has_import "$file" "$skip_import"; then
                PAT_SKIP+=( "1" )
            else
                PAT_SKIP+=( "0" )
            fi
        else
            PAT_SKIP+=( "0" )
        fi
    done

    # staged diff에서 추가된 라인 번호와 내용 추출
    while IFS=: read -r line_num line_content; do
        [[ -z "$line_num" ]] && continue

        # 주석 라인 스킵
        if is_comment_line "$line_content"; then
            continue
        fi

        # noqa 지시어 스킵
        if has_noqa "$line_content"; then
            continue
        fi

        # docstring 내부 체크 (파일이 실제로 존재할 때만)
        if [[ -f "$file" ]]; then
            if check_in_docstring "$file" "$line_num"; then
                continue
            fi
        fi

        # 패턴 반복 검사
        for (( pi=0; pi<PAT_COUNT; pi++ )); do
            pat_name="${PAT_NAMES[$pi]}"
            pat_regex="${PAT_REGEXES[$pi]}"
            pat_severity="${PAT_SEVERITIES[$pi]}"
            pat_message="${PAT_MESSAGES[$pi]}"
            pat_file_types="${PAT_FILE_TYPES[$pi]}"
            pat_skip="${PAT_SKIP[$pi]}"

            # file_types 검사: "all"이 아니면 확장자 일치 여부 확인
            if [[ "$pat_file_types" != "all" ]]; then
                # 쉼표 구분 목록에서 현재 확장자가 있는지 확인
                _type_match=0
                IFS=',' read -ra _types <<< "$pat_file_types"
                for _t in "${_types[@]}"; do
                    if [[ "$FILE_EXT" == "$_t" ]]; then
                        _type_match=1
                        break
                    fi
                done
                [[ "$_type_match" -eq 0 ]] && continue
            fi

            # skip_if_import 스킵
            if [[ "$pat_skip" == "1" ]]; then
                continue
            fi

            # 패턴 매칭
            if echo "$line_content" | grep -qE "$pat_regex"; then
                if [[ "$pat_severity" == "FAIL" ]]; then
                    if [[ "$IS_TEST" -eq 1 ]]; then
                        report_issue 1 "$file" "$line_num" \
                            "${pat_name} 하드코딩" \
                            "$pat_message"
                        (( WARNINGS++ )) || true
                    else
                        report_issue 0 "$file" "$line_num" \
                            "${pat_name} 하드코딩" \
                            "$pat_message"
                        (( VIOLATIONS++ )) || true
                    fi
                else
                    # WARN: tests/ 여부와 무관하게 항상 WARNING
                    report_issue 1 "$file" "$line_num" \
                        "${pat_name} 하드코딩" \
                        "$pat_message"
                    (( WARNINGS++ )) || true
                fi
            fi
        done

    done < <(get_staged_lines_with_numbers "$file")

done <<< "$STAGED_FILES"

# 최종 결과 출력
echo ""
if [[ "$VIOLATIONS" -gt 0 ]]; then
    echo -e "${RED}모듈화 린트: ${VIOLATIONS}개 위반 발견 — 커밋 차단됨${NC}"
    if [[ "$WARNINGS" -gt 0 ]]; then
        echo -e "${YELLOW}(tests/ 경고 ${WARNINGS}개 추가)${NC}"
    fi
    exit 1
elif [[ "$WARNINGS" -gt 0 ]]; then
    echo -e "${YELLOW}모듈화 린트: 위반 없음, tests/ 경고 ${WARNINGS}개 (커밋 허용)${NC}"
    exit 0
else
    echo -e "${GREEN}모듈화 린트: 통과${NC}"
    exit 0
fi
