"""
Tool: parse_cobol_file
Parses a single COBOL source file and extracts:
  - IDENTIFICATION DIVISION  → program metadata
  - DATA DIVISION            → working storage variables, copybooks
  - PROCEDURE DIVISION       → paragraphs and their line ranges
  - Embedded SQL             → SELECT/INSERT/UPDATE/DELETE with tables/columns
  - Embedded CICS            → SEND/RECEIVE/READ/WRITE/LINK/XCTL commands
"""

import re
import logging
from pathlib import Path

logger = logging.getLogger(__name__)

# ── Regex patterns ─────────────────────────────────────────────────────────────

# Division headers
_DIV_RE = re.compile(
    r"^\s{6,}\s*(IDENTIFICATION|ENVIRONMENT|DATA|PROCEDURE)\s+DIVISION",
    re.IGNORECASE | re.MULTILINE,
)

# IDENTIFICATION fields
_PROGRAM_ID_RE  = re.compile(r"PROGRAM-ID\s*\.\s*([A-Z0-9#@$-]+)", re.IGNORECASE)
_AUTHOR_RE      = re.compile(r"AUTHOR\s*\.\s*(.+?)(?=\n\s{6,}[A-Z]|\Z)", re.IGNORECASE | re.DOTALL)
_DATE_WRITTEN_RE= re.compile(r"DATE-WRITTEN\s*\.\s*(.+?)(?=\n\s{6,}[A-Z]|\Z)", re.IGNORECASE | re.DOTALL)

# DATA DIVISION - variable definitions (level number + name + optional PIC)
_VAR_RE = re.compile(
    r"^\s{6,}\s*(0[1-9]|[1-4][0-9]|49|66|77|88)\s+([A-Z0-9#@$-]+)(.*?)$",
    re.IGNORECASE | re.MULTILINE,
)
_PIC_RE = re.compile(r"PIC(?:TURE)?\s+(?:IS\s+)?([SX9AV()\d\-\.]+)", re.IGNORECASE)

# COPY statements
_COPY_RE = re.compile(r"^\s+COPY\s+([A-Z0-9#@$-]+)", re.IGNORECASE | re.MULTILINE)

# PROCEDURE DIVISION - paragraph names (column 8, followed by a period)
_PARA_RE = re.compile(
    r"^(\s{7,8})([A-Z0-9][A-Z0-9#@$-]*)\s*\.\s*$",
    re.IGNORECASE | re.MULTILINE,
)

# SQL statements
_SQL_RE = re.compile(
    r"EXEC\s+SQL(.*?)END-EXEC",
    re.IGNORECASE | re.DOTALL,
)
_SQL_TYPE_RE   = re.compile(r"^\s*(SELECT|INSERT|UPDATE|DELETE|DECLARE|OPEN|FETCH|CLOSE|INCLUDE)", re.IGNORECASE)
_SQL_TABLE_RE  = re.compile(r"(?:FROM|INTO|UPDATE|JOIN)\s+([A-Z0-9_#@$]+)", re.IGNORECASE)
_SQL_COLS_RE   = re.compile(r"SELECT\s+(.*?)\s+(?:INTO|FROM)", re.IGNORECASE | re.DOTALL)
_SQL_WHERE_RE  = re.compile(r"WHERE\s+(.*?)(?:END-EXEC|ORDER|GROUP|HAVING|\Z)", re.IGNORECASE | re.DOTALL)

# CICS commands
_CICS_RE = re.compile(
    r"EXEC\s+CICS\s+(.*?)END-EXEC",
    re.IGNORECASE | re.DOTALL,
)
_CICS_CMD_RE     = re.compile(r"^([A-Z]+(?:\s+[A-Z]+)?)", re.IGNORECASE)
_CICS_MAP_RE     = re.compile(r"MAP\s*\(\s*['\"]?([A-Z0-9#@$]+)['\"]?\s*\)", re.IGNORECASE)
_CICS_MAPSET_RE  = re.compile(r"MAPSET\s*\(\s*['\"]?([A-Z0-9#@$]+)['\"]?\s*\)", re.IGNORECASE)
_CICS_PROG_RE    = re.compile(r"PROGRAM\s*\(\s*['\"]?([A-Z0-9#@$-]+)['\"]?\s*\)", re.IGNORECASE)
_CICS_FILE_RE    = re.compile(r"FILE\s*\(\s*['\"]?([A-Z0-9#@$-]+)['\"]?\s*\)", re.IGNORECASE)
_CICS_QUEUE_RE   = re.compile(r"QUEUE\s*\(\s*['\"]?([A-Z0-9#@$-]+)['\"]?\s*\)", re.IGNORECASE)


def _safe_read(path: Path) -> str:
    for enc in ("utf-8", "cp1252", "cp037", "latin-1"):
        try:
            return path.read_text(encoding=enc)
        except (UnicodeDecodeError, LookupError):
            continue
    return path.read_text(encoding="utf-8", errors="replace")


def _line_number(content: str, match_start: int) -> int:
    return content[:match_start].count("\n") + 1


def _split_divisions(content: str) -> dict:
    """Split COBOL source into its four main divisions."""
    divisions = {"IDENTIFICATION": "", "ENVIRONMENT": "", "DATA": "", "PROCEDURE": ""}
    matches = list(_DIV_RE.finditer(content))
    for i, m in enumerate(matches):
        name = m.group(1).upper()
        start = m.start()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
        divisions[name] = content[start:end]
    return divisions


def _parse_identification(div: str) -> dict:
    result = {}
    m = _PROGRAM_ID_RE.search(div)
    if m:
        result["program_id"] = m.group(1).strip().rstrip(".")
    m = _AUTHOR_RE.search(div)
    if m:
        result["author"] = " ".join(m.group(1).split()).rstrip(".")
    m = _DATE_WRITTEN_RE.search(div)
    if m:
        result["date_written"] = " ".join(m.group(1).split()).rstrip(".")
    return result


def _parse_data_division(div: str) -> dict:
    variables = []
    seen = set()
    for m in _VAR_RE.finditer(div):
        level = m.group(1).lstrip("0") or "0"
        name  = m.group(2).upper()
        rest  = m.group(3)
        if name in ("FILLER",):
            continue
        pic_m = _PIC_RE.search(rest)
        pic   = pic_m.group(1) if pic_m else None
        key   = (level, name)
        if key not in seen:
            seen.add(key)
            variables.append({
                "level": level,
                "name":  name,
                "type":  f"PIC {pic}" if pic else "GROUP",
            })

    copies = list(dict.fromkeys(
        m.group(1).upper() for m in _COPY_RE.finditer(div)
    ))

    return {"working_storage": variables[:200], "copybooks_expanded": copies}


def _parse_paragraphs(proc_div: str, full_content: str) -> list:
    """Extract paragraph names and their approximate line ranges."""
    paragraphs = []
    matches = list(_PARA_RE.finditer(proc_div))

    # Offset of PROCEDURE DIVISION in original content
    proc_offset = full_content.upper().find("PROCEDURE DIVISION")
    if proc_offset == -1:
        proc_offset = 0

    for i, m in enumerate(matches):
        name  = m.group(2).upper()
        start_line = _line_number(full_content, proc_offset + m.start())
        end_line   = (
            _line_number(full_content, proc_offset + matches[i + 1].start()) - 1
            if i + 1 < len(matches)
            else full_content.count("\n") + 1
        )

        # Find PERFORM/CALL references within this paragraph's text
        para_text = proc_div[m.end(): matches[i + 1].start() if i + 1 < len(matches) else len(proc_div)]
        performs  = list(dict.fromkeys(
            pm.group(1).upper()
            for pm in re.finditer(r"\bPERFORM\s+([A-Z0-9][A-Z0-9#@$-]*)", para_text, re.IGNORECASE)
        ))

        paragraphs.append({
            "name":             name,
            "lines":            f"{start_line}-{end_line}",
            "calls_paragraphs": performs,
        })

    return paragraphs


def _parse_sql(content: str) -> list:
    statements = []
    for m in _SQL_RE.finditer(content):
        sql_body = m.group(1).strip()
        line     = _line_number(content, m.start())

        type_m = _SQL_TYPE_RE.match(sql_body)
        if not type_m:
            continue
        sql_type = type_m.group(1).upper()
        if sql_type in ("INCLUDE", "DECLARE"):
            continue

        tables  = [t.upper() for t in _SQL_TABLE_RE.findall(sql_body)]
        where_m = _SQL_WHERE_RE.search(sql_body)
        where   = " ".join(where_m.group(1).split())[:120] if where_m else None

        # Extract column list for SELECT
        columns = []
        if sql_type == "SELECT":
            cols_m = _SQL_COLS_RE.search(sql_body)
            if cols_m:
                raw = cols_m.group(1).strip()
                if raw != "*":
                    columns = [c.strip().split(".")[-1].upper() for c in raw.split(",")][:20]

        statements.append({
            "type":    sql_type,
            "tables":  tables,
            "columns": columns,
            "where":   where,
            "line":    line,
        })

    return statements


def _parse_cics(content: str) -> list:
    commands = []
    for m in _CICS_RE.finditer(content):
        body = m.group(1).strip()
        line = _line_number(content, m.start())

        cmd_m = _CICS_CMD_RE.match(body)
        if not cmd_m:
            continue
        cmd = cmd_m.group(1).upper()

        entry = {"command": cmd, "line": line}

        map_m    = _CICS_MAP_RE.search(body)
        mapset_m = _CICS_MAPSET_RE.search(body)
        prog_m   = _CICS_PROG_RE.search(body)
        file_m   = _CICS_FILE_RE.search(body)
        queue_m  = _CICS_QUEUE_RE.search(body)

        if map_m:    entry["map"]     = map_m.group(1).upper()
        if mapset_m: entry["mapset"]  = mapset_m.group(1).upper()
        if prog_m:   entry["program"] = prog_m.group(1).upper()
        if file_m:   entry["file"]    = file_m.group(1).upper()
        if queue_m:  entry["queue"]   = queue_m.group(1).upper()

        commands.append(entry)

    return commands


def parse_cobol_file(file_path: str) -> dict:
    """
    Parse a single COBOL source file and return structured data.

    Args:
        file_path: Absolute path to the .cbl or .cob file.

    Returns:
        dict with identification, data_division, procedure_division,
        sql_statements, cics_commands, and business_logic_summary scaffold.
    """
    path = Path(file_path)
    if not path.exists():
        return {"status": "error", "message": f"File not found: {file_path}"}

    content    = _safe_read(path)
    divisions  = _split_divisions(content)
    line_count = content.count("\n") + 1

    identification = _parse_identification(divisions["IDENTIFICATION"])
    data_division  = _parse_data_division(divisions["DATA"])
    paragraphs     = _parse_paragraphs(divisions["PROCEDURE"], content)
    sql_statements = _parse_sql(content)
    cics_commands  = _parse_cics(content)

    # Tables summary
    tables_read    = list(dict.fromkeys(
        t for s in sql_statements if s["type"] in ("SELECT", "FETCH") for t in s["tables"]
    ))
    tables_written = list(dict.fromkeys(
        t for s in sql_statements if s["type"] in ("INSERT", "UPDATE", "DELETE") for t in s["tables"]
    ))
    screens_used   = list(dict.fromkeys(
        c.get("mapset") or c.get("map", "")
        for c in cics_commands if c["command"] in ("SEND MAP", "RECEIVE MAP") and (c.get("mapset") or c.get("map"))
    ))
    programs_called = list(dict.fromkeys(
        c["program"] for c in cics_commands if "program" in c
    ))

    # Complexity heuristic
    total = len(paragraphs) + len(sql_statements) * 2 + len(cics_commands)
    complexity = "LOW" if total < 20 else "MEDIUM" if total < 60 else "HIGH"

    return {
        "program":     path.stem.upper(),
        "source_file": str(path),
        "line_count":  line_count,

        "identification": identification,

        "data_division": data_division,

        "procedure_division": {
            "paragraph_count": len(paragraphs),
            "paragraphs":      paragraphs,
        },

        "sql_statements": sql_statements,

        "cics_commands": cics_commands,

        "business_logic_summary": {
            "db2_tables_read":    tables_read,
            "db2_tables_written": tables_written,
            "screens_used":       screens_used,
            "programs_called":    programs_called,
            "estimated_complexity": complexity,
            "ai_summary":         None,  # filled by summarise_with_ai()
        },

        "status": "ok",
    }
