import os
import re
import shlex

from agromatrix_tools import tool_dictionary_review as review

CATEGORIES = {"field","crop","operation","material","unit"}

# Only these slash-commands are treated as operator commands.
# Everything else (e.g. /start, /agromatrix) must fall through to the normal agent flow.
OPERATOR_COMMANDS = {
    "whoami",
    "pending",
    "pending_show",
    "approve",
    "reject",
    "apply_dict",
    "pending_stats",
}


def is_operator(user_id: str | None, chat_id: str | None) -> bool:
    allowed_ids = [s.strip() for s in os.getenv('AGX_OPERATOR_IDS', '').split(',') if s.strip()]
    allowed_chat = os.getenv('AGX_OPERATOR_CHAT_ID', '').strip()
    if allowed_chat and chat_id and str(chat_id) != allowed_chat:
        return False
    if allowed_ids and user_id and str(user_id) in allowed_ids:
        return True
    return False


def parse_operator_command(text: str):
    parts = shlex.split(text)
    if not parts:
        return None
    cmd = parts[0].lstrip('/')
    args = parts[1:]
    if cmd not in OPERATOR_COMMANDS:
        return None
    return {"cmd": cmd, "args": args}


def _wrap(summary: str, details: dict | None = None):
    return {
        "status": "ok",
        "summary": summary,
        "artifacts": [],
        "tool_calls": [],
        "next_actions": [],
        "details": details or {}
    }


def _extract_ref(text: str) -> str | None:
    m = re.search(r"pending\.jsonl:\d+", text)
    return m.group(0) if m else None


def _extract_index(text: str) -> int | None:
    m = re.search(r"(\d{1,3})", text)
    if not m:
        return None
    try:
        return int(m.group(1))
    except Exception:
        return None


def _extract_limit(text: str, default: int = 10) -> int:
    m = re.search(r"(?:до|limit)\s*(\d{1,3})", text)
    if m:
        try:
            return int(m.group(1))
        except Exception:
            return default
    m = re.search(r"(\d{1,3})", text)
    if m:
        try:
            return int(m.group(1))
        except Exception:
            return default
    return default


def _extract_category(text: str) -> str | None:
    t = text.lower()
    if 'один' in t or 'unit' in t:
        return 'unit'
    if 'операц' in t or 'operation' in t:
        return 'operation'
    if 'культур' in t or 'crop' in t:
        return 'crop'
    if 'матеріал' in t or 'material' in t:
        return 'material'
    if 'поле' in t or 'field' in t:
        return 'field'
    return None


def _extract_canonical_id(text: str) -> str | None:
    m = re.search(r"(?:як|as)\s+([\w\-:.]+)", text, flags=re.IGNORECASE)
    if m:
        return m.group(1)
    return None


def _extract_reason(text: str) -> str:
    if ':' in text:
        return text.split(':', 1)[1].strip()
    m = re.search(r"(?:бо|через)\s+(.+)$", text, flags=re.IGNORECASE)
    if m:
        return m.group(1).strip()
    return ''


def _resolve_ref_by_index(last_list: list | None, idx: int | None) -> str | None:
    if not last_list or not idx:
        return None
    if idx < 1 or idx > len(last_list):
        return None
    item = last_list[idx - 1]
    return item.get('pending_ref') or item.get('ref')


def handle_whoami(user_id: str | None, chat_id: str | None):
    summary = "user_id: {}\nchat_id: {}".format(user_id or '', chat_id or '')
    return _wrap(summary)


def handle_pending(limit=10, category=None):
    items = review.list_pending(limit=limit, category=category)
    lines = []
    pending_items = []
    for item in items:
        sug = item.get('suggestions', [])[:5]
        sug_s = ', '.join([f"{s['id']}({s['score']:.2f})" for s in sug])
        lines.append(f"{item['pending_ref']} | {item.get('category')} | {item.get('raw_term')} | {sug_s}")
        pending_items.append({
            'pending_ref': item.get('pending_ref'),
            'pending_id': item.get('pending_id'),
            'raw_term': item.get('raw_term'),
            'category': item.get('category'),
            'suggestions': [{"id": s.get('id'), "score": s.get('score')} for s in sug]
        })
    summary = "\n".join(lines) if lines else "Немає pending записів."
    return _wrap(summary, {"count": len(items), "pending_items": pending_items})


def handle_pending_show(ref: str):
    detail = review.get_pending_detail(ref)
    if not detail:
        return _wrap('Не знайдено pending запис')

    lines = [
        f"ref: {detail.get('ref')}",
        f"category: {detail.get('category')}",
        f"raw_term: {detail.get('raw_term')}",
        f"ts: {detail.get('ts')}",
        f"status: {detail.get('status')}"
    ]
    if detail.get('decision'):
        lines.append(f"decision: {detail.get('decision')}")
    if detail.get('reason'):
        lines.append(f"reason: {detail.get('reason')}")

    lines.append('suggestions:')
    suggestions = detail.get('suggestions') or []
    if suggestions:
        for s in suggestions:
            score = s.get('score')
            score_s = f"{score:.2f}" if isinstance(score, (int, float)) else "n/a"
            lines.append(f"- {s.get('id')} ({score_s})")
    else:
        lines.append('- (none)')

    return _wrap("\n".join(lines), detail)

def handle_approve(ref, map_to=None, create_new=None, category=None, name=None, id_=None, apply=False):
    if map_to:
        action = {"type": "map_to_existing", "canonical_id": map_to}
    elif create_new:
        action = {"type": "create_new_entry", "canonical_id": id_ or '', "canonical_name": name or ''}
    else:
        action = {"type": "add_synonym", "canonical_id": map_to}
    res = review.approve_pending(ref, action)
    if apply:
        review.apply_resolutions()
    return _wrap(f"approved {ref}", {"record": res, "applied": apply})


def handle_reject(ref, reason):
    res = review.reject_pending(ref, reason)
    return _wrap(f"rejected {ref}", {"record": res})


def handle_apply():
    count = review.apply_resolutions()
    return _wrap(f"applied {count}")


def handle_stats():
    stats = review.stats()
    return _wrap(f"open={stats['open']} approved={stats['approved']} rejected={stats['rejected']}", stats)


def route_operator_text(text: str, user_id: str | None, chat_id: str | None, last_pending_list: list | None = None):
    if not is_operator(user_id, chat_id):
        return {
            "status": "error",
            "summary": "Недостатньо прав",
            "artifacts": [],
            "tool_calls": [],
            "next_actions": []
        }

    t = text.strip().lower()

    # list pending
    if any(k in t for k in ['покажи', 'показати', 'невпізнан', 'непізнан', 'pending', 'невідом']):
        category = _extract_category(t)
        limit = _extract_limit(t, default=10)
        if limit < 1:
            limit = 1
        if limit > 100:
            limit = 100
        if category and category not in CATEGORIES:
            return _wrap('unknown category')
        return handle_pending(limit=limit, category=category)

    # stats
    if any(k in t for k in ['статист', 'скільки pending', 'pending stats']):
        return handle_stats()

    # apply
    if any(k in t for k in ['застосуй', 'apply', 'застосувати', 'застосуй зміни']):
        return handle_apply()

    # details
    if any(k in t for k in ['деталі', 'detail', 'подробиц', 'покажи деталі']):
        ref = _extract_ref(t)
        if not ref:
            idx = _extract_index(t)
            ref = _resolve_ref_by_index(last_pending_list, idx)
        if not ref:
            return _wrap('Немає ref або контексту для деталей')
        return handle_pending_show(ref)

    # approve
    if any(k in t for k in ['підтверд', 'approve', 'схвали']):
        ref = _extract_ref(t)
        if not ref:
            idx = _extract_index(t)
            ref = _resolve_ref_by_index(last_pending_list, idx)
        if not ref:
            return _wrap('Немає ref або контексту для підтвердження')
        canonical_id = _extract_canonical_id(t)
        if not canonical_id:
            return _wrap('Вкажіть canonical_id після "як"')
        return handle_approve(ref, map_to=canonical_id)

    # reject
    if any(k in t for k in ['відхил', 'reject', 'забракуй']):
        ref = _extract_ref(t)
        if not ref:
            idx = _extract_index(t)
            ref = _resolve_ref_by_index(last_pending_list, idx)
        if not ref:
            return _wrap('Немає ref або контексту для відхилення')
        reason = _extract_reason(text)
        if not reason:
            return _wrap('Вкажіть причину відхилення')
        return handle_reject(ref, reason)

    return None


def route_operator_command(text: str, user_id: str | None, chat_id: str | None):
    parsed = parse_operator_command(text)
    if not parsed:
        return None
    if not is_operator(user_id, chat_id):
        return {
            "status": "error",
            "summary": "Недостатньо прав",
            "artifacts": [],
            "tool_calls": [],
            "next_actions": []
        }

    cmd = parsed['cmd']
    args = parsed['args']

    if cmd == 'whoami':
        return handle_whoami(user_id, chat_id)

    if cmd == 'pending_show':
        if not args:
            return _wrap('Потрібен ref')
        return handle_pending_show(args[0])

    if cmd == 'pending':
        limit = 10
        category = None
        i = 0
        while i < len(args):
            if args[i] == '--limit' and i + 1 < len(args):
                try:
                    limit = int(args[i+1])
                except Exception:
                    limit = 10
                i += 2
                continue
            if args[i] == '--category' and i + 1 < len(args):
                category = args[i+1]
                i += 2
                continue
            i += 1
        # clamp
        if limit < 1:
            limit = 1
        if limit > 100:
            limit = 100
        if category and category not in CATEGORIES:
            return _wrap('unknown category')
        return handle_pending(limit=limit, category=category)

    if cmd == 'approve':
        if not args:
            return _wrap('missing ref')
        ref = args[0]
        map_to = None
        create_new = False
        category = None
        name = None
        id_ = None
        apply = False
        i = 1
        while i < len(args):
            if args[i] == 'map_to':
                map_to = args[i+1]
                i += 2
            elif args[i] == '--apply':
                apply = True
                i += 1
            elif args[i] == 'create_new':
                create_new = True
                i += 1
            elif args[i] == 'category':
                category = args[i+1]
                i += 2
            elif args[i] == 'name':
                name = args[i+1]
                i += 2
            elif args[i] == 'id':
                id_ = args[i+1]
                i += 2
            else:
                i += 1
        if apply:
            allow_apply = os.getenv('AGX_ALLOW_APPLY', '0') == '1' or os.getenv('AGX_OPS_MODE', '0') == '1'
            if not allow_apply:
                return _wrap('apply_not_allowed')
        return handle_approve(ref, map_to=map_to, create_new=create_new, category=category, name=name, id_=id_, apply=apply)

    if cmd == 'reject':
        if len(args) < 2:
            return _wrap('missing ref or reason')
        ref = args[0]
        reason = ' '.join(args[1:])
        return handle_reject(ref, reason)

    if cmd == 'apply_dict':
        return handle_apply()

    if cmd == 'pending_stats':
        return handle_stats()

    return _wrap('unknown command')
