"""Condensa LLM-Powered Encoder (v0.3).

Replaces the regex-based encoder with an LLM that understands the full
Condensa specification. Achieves near-perfect round-trip fidelity by
using semantic understanding rather than pattern matching.

Usage:
    # With Anthropic API key:
    export ANTHROPIC_API_KEY=sk-ant-...
    python -m src.llm_encoder "Search the web for SpaceX news, top 5 results"

    # Without API key (prints the prompt for manual use):
    python -m src.llm_encoder --prompt "Search the web for SpaceX news"

    # Batch mode:
    python -m src.llm_encoder --batch input.txt

    # Compare with regex encoder:
    python -m src.llm_encoder --compare "Search the web for SpaceX news"
"""

import os
import sys
import json
from pathlib import Path

# Add project root to path

from .utils import count_tokens

# ─── The Condensa v0.3 System Prompt ──────────────────────────────

CONDENSA_SYSTEM_PROMPT = """You are a Condensa v0.3 encoder. Convert natural language AI-to-AI messages into Condensa notation.

## Condensa Quick Reference

MESSAGE STRUCTURE: [urgency] type:body

TYPES:
  !   command (fire-and-forget)
  !?  sync command (expects response — command + query)
  ?   query (request info)
  =   result (return data)
  >   delegate (forward to agent: >:@AgentName action)
  <   receive (acknowledge)
  ~   update (modify state: ~:^ inherit prev, ~:& extend)
  #   status (report progress: #:done, #:run, #:wait, #:fail)
  X   cancel
  E   error
  @   meta (protocol: @:policy, @:define, @:caps, @:verbose, @:on)

URGENCY: +++ critical  ++ high  + normal (default, omit)  - low  -- background

ACTIONS (use these abbreviations):
  srch=search  filt=filter  sort=sort  grp=group_by  agg=aggregate
  cnt=count  avg=average  sum=sum  xfm=transform  mrg=merge
  splt=split  dedup=deduplicate  gen=generate  sumz=summarize
  xlat=translate  fmt=format  val=validate  cmp=compare  cls=classify
  ext=extract  enr=enrich  rank=rank  call=api_call  read=read/fetch
  wrt=write/store  del=delete  exec=execute  wait=wait  retry=retry  log=log

FLOW CONTROL:
  A | B | C          pipeline (implicit verb mode: bare verbs after first !:)
  seq(A; B; C)       sequential
  par(A; B; C)       parallel
  if(cond) A else B  conditional
  [expr]             inline evaluation

MODIFIERS (slash-prefixed):
  /n:N  /fmt:X  /lang:X  /since:T  /top  /bot  /asc  /desc  /timeout:T
  /each (apply to each element)  /scan (cumulative)  /repeat:N (repeat N times)

CONTEXT & REFS:
  ^       inherit previous context
  ^N      inherit from message N
  ^.field inherit one field
  $_      last result (implicit in pipelines)
  $1      result of step 1
  $turn   turn number
  $agent  current agent
  $t      timestamp

FALLBACK:
  fb:cache  fb:skip  fb:abort  fb:default  fb:ask  fb:retry:N  fb:degrade

ERROR ROUTING:
  @:on E:code fb:strategy    (pattern-matched error handler)

CONFIDENCE (append to values):
  ! certain  !! verified  ~ likely  ~~ uncertain  ??? guess

SAFE ACCESS: ref.field? (null if missing)

BULK LITERAL: <<<raw payload>>>

## Rules

1. Output ONLY the Condensa encoding. No explanation, no markdown, no quotes.
2. Use the most compact form possible.
3. In pipelines, only the first step gets !: — subsequent steps are bare verbs.
4. Use !?: for commands that explicitly expect a response (synchronous/blocking).
5. Use fb: for fallbacks (not //).
6. Use /each, /scan, /repeat:N as modifiers (not verb suffixes).
7. Use ^ to inherit context from previous messages.
8. Use $_ for the last result. In pipelines, $_ is implicit and can be omitted.
9. Omit urgency prefix for normal priority.
10. Use @:policy for session-level defaults.
11. Use @:on for error-pattern routing.
12. Preserve all semantic content — zero information loss.
13. Use <<<>>> for complex structured payloads (JSON, etc).
14. Use ? suffix for optional/safe field access.

## Examples

NL: "Search the web for SpaceX Starship news, return top 5, format as bullets."
CDN: !:srch 'SpaceX Starship' src:web /n:5 /fmt:bullets

NL: "Filter active users, group by region, calculate average revenue, sort descending."
CDN: !:filt status:active | grp region | avg revenue | sort /desc

NL: "Error 429 rate limit. Retry 3 times, wait 30 seconds, exponential backoff. If all fail, return degraded result."
CDN: E:429 retry:3 wait:30s backoff:exp fb:degrade

NL: "The previous response needs correction. Keep section 1, revise section 2 with more detail, delete section 3."
CDN: ~:^ s1:keep s2:revise+detail s3:del

NL: "In parallel: translate report to Spanish, summarize in 200 words, extract entities as JSON. Then merge."
CDN: par(xlat /docs/report.md /lang:es; sumz $_ /n:200; ext entities /fmt:json) | mrg

NL: "URGENT: Read the inventory API synchronously with 15 second timeout. If it fails, use cache."
CDN: ++!?:read https://api.inventory.io/v2/items /timeout:15s fb:cache

NL: "Set session defaults: retry 3 times, timeout 30 seconds, degrade on failure."
CDN: @:policy retry:3 timeout:30s fb:degrade

NL: "Translate the API docs into Spanish, French, German, Japanese, Korean. Each language separately. Preserve code blocks. Use ISO terms. Output markdown. Include glossary."
CDN: !:xlat /docs/api_ref.md /lang:(es,fr,de,ja,ko) /each preserve:code_blocks terms:iso /fmt:md +glossary

NL: "Delegate to AgentC: review the code at the previous result's path. Check style, bugs, performance, security."
CDN: >:@AgentC review $_.path checks:(style,bugs,perf,security)

NL: "On 429 errors degrade, on 5xx abort, on timeout retry 3 times."
CDN: @:on E:429 fb:degrade\\n@:on E:5xx fb:abort\\n@:on E:timeout fb:retry:3

NL: "Status update: processing complete. 4 of 4 stages done. 12847 records processed. 0 failures. Output at /tmp/results.json."
CDN: #:done 4/4 records:12847 fail:0 out:/tmp/results.json

NL: "Cancel everything and discard partial results."
CDN: X: all discard:T

NL: "The code review found 3 issues. Style and types passed. Issues: line 23 high severity SQL injection, line 45 medium severity O(n2) loop."
CDN: =:review issues:3 pass:(style,types) <<<[{l:23,sev:high,cat:security,msg:"sql_injection"},{l:45,sev:med,cat:perf,msg:"O(n2) loop"}]>>>

NL: "Take the previous results, fix each issue: line 23 replace string format with parameterized query, line 45 replace loop with defaultdict. Then run tests."
CDN: ^$_ | fix /each seq(l:23 str_fmt->param; l:45 loop->defaultdict) | exec tests
"""


# ─── LLM Encoder Class ───────────────────────────────────────────

class LLMEncoder:
    """Encodes natural language to Condensa using an LLM."""

    def __init__(self, model: str = "claude-sonnet-4-20250514", api_key: str | None = None):
        self.model = model
        self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
        self._client = None

    @property
    def client(self):
        if self._client is None:
            if not self.api_key:
                raise RuntimeError(
                    "No API key. Set ANTHROPIC_API_KEY or pass api_key= to LLMEncoder.\n"
                    "Alternatively, use get_prompt() to get the prompt for manual use."
                )
            import anthropic
            self._client = anthropic.Anthropic(api_key=self.api_key)
        return self._client

    def encode(self, text: str) -> str:
        """Encode natural language to Condensa using the LLM."""
        response = self.client.messages.create(
            model=self.model,
            max_tokens=1024,
            system=CONDENSA_SYSTEM_PROMPT,
            messages=[{"role": "user", "content": text.strip()}],
            temperature=0,
        )
        return response.content[0].text.strip()

    def encode_with_stats(self, text: str) -> dict:
        """Encode and return compression stats."""
        condensa = self.encode(text)
        nl_tokens = count_tokens(text)
        c_tokens = count_tokens(condensa)
        return {
            "original": text,
            "condensa": condensa,
            "nl_tokens": nl_tokens,
            "condensa_tokens": c_tokens,
            "reduction_pct": round((1 - c_tokens / nl_tokens) * 100, 1) if nl_tokens > 0 else 0,
            "encoder": "llm",
            "model": self.model,
        }

    def encode_batch(self, texts: list[str]) -> list[dict]:
        """Encode multiple messages."""
        return [self.encode_with_stats(t) for t in texts]

    @staticmethod
    def get_prompt(text: str) -> str:
        """Get the full prompt for manual use (paste into any LLM)."""
        return (
            "SYSTEM:\n"
            + CONDENSA_SYSTEM_PROMPT
            + "\n\nUSER:\n"
            + text.strip()
        )

    @staticmethod
    def get_system_prompt() -> str:
        """Get just the system prompt."""
        return CONDENSA_SYSTEM_PROMPT


# ─── Comparison with regex encoder ────────────────────────────────

def compare_encoders(text: str, llm_result: str | None = None) -> dict:
    """Compare regex encoder vs LLM encoder for the same input."""
    from .encoder import encode as regex_encode
    from .decoder import decode

    regex_result = regex_encode(text)
    regex_decoded = decode(regex_result)
    regex_tokens = count_tokens(regex_result)
    nl_tokens = count_tokens(text)

    result = {
        "original": text,
        "nl_tokens": nl_tokens,
        "regex_condensa": regex_result,
        "regex_tokens": regex_tokens,
        "regex_decoded": regex_decoded,
        "regex_reduction_pct": round((1 - regex_tokens / nl_tokens) * 100, 1) if nl_tokens > 0 else 0,
    }

    if llm_result:
        llm_decoded = decode(llm_result)
        llm_tokens = count_tokens(llm_result)
        result.update({
            "llm_condensa": llm_result,
            "llm_tokens": llm_tokens,
            "llm_decoded": llm_decoded,
            "llm_reduction_pct": round((1 - llm_tokens / nl_tokens) * 100, 1) if nl_tokens > 0 else 0,
        })

    return result


# ─── Test scenarios ───────────────────────────────────────────────

TEST_SCENARIOS = [
    "Search the web for SpaceX Starship news and return the top 5 results formatted as bullet points.",
    "Read the user profile from the database and return it in JSON format. This is a synchronous call.",
    "Translate the document from English to Spanish.",
    "Generate a 200-word summary of the quarterly report.",
    "Filter the dataset to only include active users, then sort by registration date descending.",
    "Extract all named entities from the article and return them as structured JSON.",
    "Compare these two versions of the report and highlight the differences.",
    "Validate the input data against the schema and report any errors.",
    "Error: the API returned a 429 rate limit error. Retry 3 times with 30 second wait and exponential backoff. If all retries fail, return a degraded result.",
    "Processing complete. 4 out of 4 stages done. 12,847 records processed. 0 failures. Output at /tmp/results.json.",
    "Cancel all running tasks and discard any partial results.",
    "The previous answer was partially correct. Keep section 1, revise section 2 with more detail, and delete section 3.",
    "Set default retry to 3, default timeout of 30 seconds, and default fallback to degrade.",
    "Execute three tasks in parallel: translate the report to Spanish, summarize it in 200 words, and extract named entities as JSON. Then merge the results.",
    "First, read the orders from the last 7 days. Then filter for amounts over 100. Group by category. Calculate sum and average of amount.",
    "URGENT: Production incident. Read error logs from checkout-svc in eu-west-1 for the last 10 minutes. Simultaneously check health of payment-svc, inventory-svc, and notification-svc.",
    "Delegate to AgentC: review the code at the previous result's file path. Check for style, bugs, performance, and security issues.",
    "Translate the API reference docs into Spanish, French, German, Japanese, and Korean. Each language separately. Preserve all code blocks. Use ISO standard terminology. Keep markdown format. Include a glossary.",
    "Update the session: language changed from English to Spanish, budget increased from 500 to 750.",
    "All fixes applied: 2 of 2 complete. Tests: 25 of 25 passing. Coverage: 96%. Ready for deployment.",
]


# ─── CLI ──────────────────────────────────────────────────────────

def main():
    import argparse

    parser = argparse.ArgumentParser(description="Condensa LLM Encoder (v0.3)")
    parser.add_argument("text", nargs="?", help="Natural language text to encode")
    parser.add_argument("--prompt", action="store_true", help="Print the full prompt instead of calling the API")
    parser.add_argument("--system-prompt", action="store_true", help="Print just the system prompt")
    parser.add_argument("--compare", action="store_true", help="Compare regex vs LLM encoding")
    parser.add_argument("--batch", type=str, help="Encode all lines from a file")
    parser.add_argument("--test", action="store_true", help="Run test scenarios (requires API key)")
    parser.add_argument("--test-manual", action="store_true", help="Print test scenarios as prompts for manual use")
    parser.add_argument("--model", default="claude-sonnet-4-20250514", help="Model to use")
    args = parser.parse_args()

    if args.system_prompt:
        print(CONDENSA_SYSTEM_PROMPT)
        return

    if args.test_manual:
        print("=" * 70)
        print("CONDENSA LLM ENCODER — MANUAL TEST SCENARIOS")
        print("=" * 70)
        print()
        print("Paste the system prompt below into any LLM, then send each")
        print("scenario as a user message. Record the Condensa output.")
        print()
        print("SYSTEM PROMPT:")
        print("-" * 70)
        print(CONDENSA_SYSTEM_PROMPT)
        print("-" * 70)
        print()
        for i, scenario in enumerate(TEST_SCENARIOS, 1):
            print(f"SCENARIO {i}:")
            print(f"  {scenario}")
            print()
        return

    if args.test:
        encoder = LLMEncoder(model=args.model)
        print("=" * 70)
        print("CONDENSA LLM ENCODER — AUTOMATED TEST")
        print(f"Model: {args.model}")
        print("=" * 70)

        total_nl = 0
        total_cdn = 0
        results = []

        for i, scenario in enumerate(TEST_SCENARIOS, 1):
            stats = encoder.encode_with_stats(scenario)
            total_nl += stats["nl_tokens"]
            total_cdn += stats["condensa_tokens"]
            results.append(stats)

            print(f"\n{i:2d}. NL ({stats['nl_tokens']}t): {scenario[:70]}...")
            print(f"    CDN ({stats['condensa_tokens']}t): {stats['condensa']}")
            print(f"    Reduction: {stats['reduction_pct']}%")

        overall = round((1 - total_cdn / total_nl) * 100, 1)
        print()
        print("=" * 70)
        print(f"TOTAL: {total_nl} NL tokens -> {total_cdn} CDN tokens ({overall}% reduction)")
        print("=" * 70)

        # Compare with regex
        from .encoder import encode as regex_encode
        total_regex = sum(count_tokens(regex_encode(s)) for s in TEST_SCENARIOS)
        regex_pct = round((1 - total_regex / total_nl) * 100, 1)
        print(f"\nRegex encoder: {total_nl} -> {total_regex} tokens ({regex_pct}% reduction)")
        print(f"LLM encoder:   {total_nl} -> {total_cdn} tokens ({overall}% reduction)")
        print(f"LLM advantage: {overall - regex_pct:+.1f}% better compression")
        return

    if not args.text and not args.batch:
        parser.print_help()
        return

    texts = []
    if args.batch:
        with open(args.batch) as f:
            texts = [line.strip() for line in f if line.strip()]
    elif args.text:
        texts = [args.text]

    if args.prompt:
        for text in texts:
            print(LLMEncoder.get_prompt(text))
            print()
        return

    if args.compare:
        encoder = LLMEncoder(model=args.model)
        for text in texts:
            llm_result = encoder.encode(text)
            comp = compare_encoders(text, llm_result)
            print(f"NL ({comp['nl_tokens']}t): {text}")
            print(f"Regex ({comp['regex_tokens']}t): {comp['regex_condensa']}")
            print(f"  LLM ({comp['llm_tokens']}t): {comp['llm_condensa']}")
            print(f"  Regex reduction: {comp['regex_reduction_pct']}%")
            print(f"  LLM reduction:   {comp['llm_reduction_pct']}%")
            print()
        return

    # Default: encode with API
    encoder = LLMEncoder(model=args.model)
    for text in texts:
        result = encoder.encode(text)
        print(result)


if __name__ == "__main__":
    main()
