"""
Command-line interface for educational content evaluator.

This module provides a CLI for evaluating educational content, supporting
both JSON batch input (same format as API) and raw content mode.
"""

import argparse
import asyncio
import json
import logging
import sys
from pathlib import Path
from typing import Optional

from ..core.input_models import ContentItem, RequestMetadata
from ..core.processor import BatchProcessor
from ..service import EvaluationService
from ..config.settings import settings
from .. import __version__


def setup_logging(verbose: bool = False):
    """Set up logging configuration."""
    level = logging.DEBUG if verbose else logging.WARNING
    logging.basicConfig(
        level=level,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )


def get_sample_content() -> dict:
    """Get sample content.json structure."""
    return {
        "generated_content": [
            {
                "id": "example-1",
                "curriculum": "common_core",
                "request": {
                    "grade": "7",
                    "subject": "mathematics",
                    "type": "mcq",
                    "difficulty": "medium",
                    "locale": "en-US",
                    "skills": {
                        "lesson_title": "Solving Linear Equations",
                        "substandard_id": "CCSS.MATH.7.EE.A.1"
                    },
                    "instruction": "Create a linear equation problem"
                },
                "content": {
                    "question": "What is the value of x in 3x + 7 = 22?",
                    "answer": "C",
                    "options": [
                        {"key": "A", "text": "3"},
                        {"key": "B", "text": "4"},
                        {"key": "C", "text": "5"},
                        {"key": "D", "text": "6"}
                    ]
                }
            },
            {
                "id": "example-2",
                "content": "What is the capital of France?"
            }
        ]
    }


def example_command() -> int:
    """Create a sample content.json file."""
    output_path = Path("content.json")

    if output_path.exists():
        print(f"File already exists: {output_path}")
        response = input("Overwrite? [y/N]: ").strip().lower()
        if response != 'y':
            print("Aborted.")
            return 1

    sample = get_sample_content()

    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(sample, f, indent=2)

    print(f"Created {output_path}")
    print("\nEdit the file, then run:")
    print(f"  inceptbench evaluate {output_path}")

    return 0


def is_file_path(value: str, extensions: tuple[str, ...]) -> bool:
    """Check if value looks like a file path with given extensions."""
    if not value:
        return False
    path = Path(value)
    return path.suffix.lower() in extensions and path.exists()


def read_file_or_string(
    value: str,
    extensions: tuple[str, ...]
) -> tuple[str, bool]:
    """
    Read content from file if it's a file path, otherwise return as string.

    Args:
        value: String value or file path
        extensions: Allowed file extensions (e.g., ('.txt', '.md'))

    Returns:
        Tuple of (content, was_file)
    """
    if is_file_path(value, extensions):
        path = Path(value)
        content = path.read_text(encoding='utf-8')
        return (content, True)
    return (value, False)


def load_json_content(file_path: Path) -> list[ContentItem]:
    """
    Load content items from a JSON file.

    Supports two formats:
    1. {"generated_content": [...]}
    2. [...]  (direct array)

    Args:
        file_path: Path to JSON file

    Returns:
        List of ContentItem objects

    Raises:
        ValueError: If JSON is invalid or has wrong structure
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON: {e}")

    # Handle both formats
    if isinstance(data, dict):
        if 'generated_content' not in data:
            raise ValueError(
                "JSON must have 'generated_content' array or be an array"
            )
        items_data = data['generated_content']
    elif isinstance(data, list):
        items_data = data
    else:
        raise ValueError("JSON must be an object or array")

    if not items_data:
        raise ValueError("No content items found in JSON")

    if len(items_data) > 100:
        raise ValueError("Maximum 100 items allowed per file")

    # Convert to ContentItem objects
    items = []
    for i, item_data in enumerate(items_data):
        try:
            item = ContentItem(**item_data)
            items.append(item)
        except Exception as e:
            raise ValueError(f"Invalid item at index {i}: {e}")

    return items


def create_raw_content_item(
    content: str,
    curriculum: str,
    generation_prompt: Optional[str]
) -> ContentItem:
    """
    Create a ContentItem from raw content.

    Args:
        content: Raw content string
        curriculum: Curriculum to use
        generation_prompt: Optional generation prompt

    Returns:
        ContentItem object
    """
    request = None
    if generation_prompt:
        request = RequestMetadata(instructions=generation_prompt)

    return ContentItem(
        content=content,
        curriculum=curriculum,
        request=request
    )


def build_summary(result_dict: dict, name: str = "content") -> dict:
    """
    Build a summary of evaluations including nested subcontent.

    Args:
        result_dict: The evaluation result dictionary
        name: Name/identifier for this content piece

    Returns:
        Dictionary with content names and their scores
    """
    summary = {}

    overall = result_dict.get('overall', {})
    summary[name] = {
        "overall_score": overall.get('score'),
        "overall_rating": result_dict.get('overall_rating')
    }

    # Add nested subcontent evaluations recursively
    subcontent = result_dict.get('subcontent_evaluations')
    if subcontent:
        for i, sub_eval in enumerate(subcontent, 1):
            content_type = sub_eval.get('content_type', 'item')
            sub_name = f"{name}/{content_type}_{i}"
            sub_summary = build_summary(sub_eval, sub_name)
            summary.update(sub_summary)

    return dict(sorted(summary.items()))


def print_evaluation_summary(
    evaluations: dict,
    failed_items: list,
    verbose: bool
):
    """Print evaluation summary to console."""
    print("\n" + "=" * 70)
    print("EVALUATION RESULTS")
    print("=" * 70)

    for item_id, result in evaluations.items():
        content_type = result.get('content_type', 'unknown')
        overall_score = result.get('overall', {}).get('score', 0)
        overall_rating = result.get('overall_rating', 'N/A')

        print(f"\n[{item_id}]")
        print(f"  Type: {content_type}")
        print(f"  Overall Score: {overall_score:.2f}")
        print(f"  Rating: {overall_rating}")

        # Show subcontent count if present
        subcontent = result.get('subcontent_evaluations')
        if subcontent:
            print(f"  Nested Items: {len(subcontent)}")

        if verbose:
            # Show all metrics
            print("  Metrics:")
            for key, value in result.items():
                if isinstance(value, dict) and 'score' in value:
                    print(f"    {key}: {value['score']:.2f}")

    if failed_items:
        print("\n" + "-" * 70)
        print("FAILED ITEMS:")
        for item in failed_items:
            print(f"  [{item.item_id}]: {item.error}")

    print("\n" + "=" * 70)


async def run_evaluation(
    items: list[ContentItem],
    output_file: Optional[Path],
    verbose: bool
) -> int:
    """
    Run evaluation on content items.

    Args:
        items: List of ContentItem to evaluate
        output_file: Optional file to save results
        verbose: Whether to show verbose output

    Returns:
        Exit code (0 for success, 1 for failure)
    """
    try:
        service = EvaluationService()
        processor = BatchProcessor(service=service)

        total = len(items)
        print(f"\nEvaluating {total} item(s)...")

        # Progress tracking
        if total > 1:
            try:
                from tqdm import tqdm
                pbar = tqdm(total=total, desc="Evaluating", unit="item")

                def on_progress(completed: int, total: int):
                    pbar.n = completed
                    pbar.refresh()

                result = await processor.process_batch(items, on_progress)
                pbar.close()
            except ImportError:
                # tqdm not available, simple progress
                print("Processing... (install tqdm for progress bar)")
                result = await processor.process_batch(items)
        else:
            result = await processor.process_batch(items)

        # Build output
        output_data = {
            "success_count": result.success_count,
            "failure_count": result.failure_count,
            "evaluations": result.evaluations,
            "failed_items": [
                {"item_id": fi.item_id, "error": fi.error}
                for fi in result.failed_items
            ] if result.failed_items else None
        }

        # Save to file if requested
        if output_file:
            with open(output_file, 'w', encoding='utf-8') as f:
                json.dump(output_data, f, indent=2)
            print(f"\nResults saved to: {output_file}")

        # Print summary
        print_evaluation_summary(
            result.evaluations,
            result.failed_items,
            verbose
        )

        # Print full JSON if verbose
        if verbose:
            print("\n" + "=" * 70)
            print("FULL JSON OUTPUT")
            print("=" * 70)
            print(json.dumps(output_data, indent=2))

        # Return success if at least one evaluation succeeded
        return 0 if result.success_count > 0 else 1

    except Exception as e:
        print(f"\nError: {e}", file=sys.stderr)
        if verbose:
            import traceback
            traceback.print_exc()
        return 1


def evaluate_command(args: argparse.Namespace) -> int:
    """Handle the evaluate command."""
    # Validate mutual exclusivity
    has_json = args.content_json is not None
    has_raw = args.raw is not None

    if not has_json and not has_raw:
        print("Error: Either content_json or --raw is required", file=sys.stderr)
        print("Usage: inceptbench evaluate <content.json>")
        print("   or: inceptbench evaluate --raw <content>")
        return 1

    # Validate --curriculum and --generation-prompt only with --raw
    if has_json:
        if args.curriculum != settings.DEFAULT_CURRICULUM:
            print(
                "Error: --curriculum is only valid with --raw",
                file=sys.stderr
            )
            return 1
        if args.generation_prompt:
            print(
                "Error: --generation-prompt is only valid with --raw",
                file=sys.stderr
            )
            return 1

    # Load content items
    try:
        if has_json:
            # JSON file mode
            json_path = Path(args.content_json)

            if not json_path.exists():
                print(f"Error: File not found: {json_path}", file=sys.stderr)
                return 1

            if json_path.suffix.lower() != '.json':
                print(
                    f"Error: Expected .json file, got: {json_path.suffix}",
                    file=sys.stderr
                )
                print("For raw content, use: --raw <content>")
                return 1

            items = load_json_content(json_path)
            print(f"Loaded {len(items)} item(s) from {json_path}")

        else:
            # Raw content mode
            raw_value = args.raw
            extensions = ('.txt', '.md')

            # Check if it's a file
            raw_path = Path(raw_value)
            if raw_path.exists():
                if raw_path.suffix.lower() not in extensions:
                    print(
                        f"Error: Expected .txt or .md file, got: "
                        f"{raw_path.suffix}",
                        file=sys.stderr
                    )
                    return 1
                content = raw_path.read_text(encoding='utf-8')
                print(f"Loaded content from {raw_path}")
            else:
                # Treat as string content
                content = raw_value

            if not content.strip():
                print("Error: Content cannot be empty", file=sys.stderr)
                return 1

            # Handle generation prompt (string or file)
            generation_prompt = None
            if args.generation_prompt:
                prompt_path = Path(args.generation_prompt)
                if prompt_path.exists():
                    if prompt_path.suffix.lower() not in extensions:
                        print(
                            f"Error: Expected .txt or .md file for prompt, "
                            f"got: {prompt_path.suffix}",
                            file=sys.stderr
                        )
                        return 1
                    generation_prompt = prompt_path.read_text(encoding='utf-8')
                    print(f"Loaded prompt from {prompt_path}")
                else:
                    generation_prompt = args.generation_prompt

            items = [create_raw_content_item(
                content=content,
                curriculum=args.curriculum,
                generation_prompt=generation_prompt
            )]

    except ValueError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1
    except Exception as e:
        print(f"Error loading content: {e}", file=sys.stderr)
        return 1

    # Run evaluation
    setup_logging(args.verbose)
    return asyncio.run(run_evaluation(
        items=items,
        output_file=args.output,
        verbose=args.verbose
    ))


def create_parser() -> argparse.ArgumentParser:
    """Create the argument parser."""
    parser = argparse.ArgumentParser(
        prog="inceptbench",
        description="Educational Content Evaluator CLI",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )

    # Version flag
    parser.add_argument(
        "-V", "--version",
        action="version",
        version=f"%(prog)s {__version__}"
    )

    subparsers = parser.add_subparsers(
        dest="command",
        title="commands",
        description="Available commands"
    )

    # Example command
    subparsers.add_parser(
        "example",
        help="Create a sample content.json file",
        description="Create a sample content.json file in the current directory"
    )

    # Evaluate command
    eval_parser = subparsers.add_parser(
        "evaluate",
        help="Evaluate educational content",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="Evaluate educational content quality",
        epilog="""
Input (mutually exclusive - choose one):
  content_json              JSON file with content to evaluate (API format)
  --raw CONTENT             Raw content: string or path to .txt/.md file

Raw mode options (only valid with --raw):
  --curriculum CURRICULUM   Curriculum for evaluation (default: common_core)
  --generation-prompt TEXT  Generation prompt: string or path to .txt/.md file

Examples:
  # Create a sample content.json to get started
  inceptbench example
  
  # Evaluate using JSON file (batch mode)
  inceptbench evaluate content.json
  
  # Evaluate raw content (single item)
  inceptbench evaluate --raw "What is 2+2?"
  inceptbench evaluate --raw question.txt --curriculum ngss
  inceptbench evaluate --raw content.md --generation-prompt prompt.txt

JSON Format (same as API):
  {"generated_content": [{"content": "...", "curriculum": "...", "request": {...}}]}
  
  Or just the array:
  [{"content": "..."}, {"content": "..."}]

Note: Use 'inceptbench example' to generate a sample content.json file.
        """
    )

    # Positional argument for JSON file (optional)
    eval_parser.add_argument(
        "content_json",
        nargs="?",
        help="JSON file with content to evaluate"
    )

    # Raw content flag
    eval_parser.add_argument(
        "--raw",
        metavar="CONTENT",
        help="Raw content: string or path to .txt/.md file"
    )

    # Raw mode options
    eval_parser.add_argument(
        "--curriculum",
        default=settings.DEFAULT_CURRICULUM,
        help=f"Curriculum for evaluation (default: {settings.DEFAULT_CURRICULUM})"
    )
    eval_parser.add_argument(
        "--generation-prompt",
        metavar="TEXT",
        help="Generation prompt: string or path to .txt/.md file"
    )

    # Common options
    eval_parser.add_argument(
        "-o", "--output",
        type=Path,
        metavar="FILE",
        help="Save results to JSON file"
    )
    eval_parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Show verbose output and debug logging"
    )

    return parser


def main(args: Optional[argparse.Namespace] = None) -> int:
    """
    Main CLI entry point.

    Args:
        args: Pre-parsed arguments (for use when called from legacy wrapper)

    Returns:
        Exit code
    """
    if args is None:
        parser = create_parser()
        args = parser.parse_args()

    if args.command is None:
        parser = create_parser()
        parser.print_help()
        return 0

    if args.command == "example":
        return example_command()
    elif args.command == "evaluate":
        return evaluate_command(args)
    else:
        print(f"Unknown command: {args.command}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())

