"""Output formatting, score conversion, and result writing."""

import csv
import json
import os
import sys
import webbrowser
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple

from cli_logging.cli_logger import emit_structured_log
from cli_logging.logging_utils import Operation
from common import (
    DEFAULT_PASS_THRESHOLD,
    RELEVANCE,
    COHERENCE,
    GROUNDEDNESS,
    SIMILARITY,
    TOOL_CALL_ACCURACY,
    CITATIONS,
    EXACT_MATCH,
    PARTIAL_MATCH,
    RETRIEVAL_QUERY,
    RETRIEVAL_RESULT,
    METRIC_IDS,
    STATUS_PASS,
    STATUS_FAIL,
    STATUS_ERROR,
    STATUS_PARTIAL,
    STATUS_UNKNOWN,
    pascal_case_to_title,
    RunConfig,
)
from generate_report import generate_html_report, calculate_aggregate_statistics
from schema_handler import SchemaVersionManager


def write_results_to_html(results: List[Dict], output_file: str,
                          agent_name: Optional[str] = None, agent_id: Optional[str] = None,
                          cli_version: Optional[str] = None, judge: Optional[str] = None):
    """Write results to HTML file using generate_html_report from generate_report.py."""
    try:
        html = generate_html_report(results, agent_name=agent_name, agent_id=agent_id,
                                    cli_version=cli_version, judge=judge)
        os.makedirs(os.path.dirname(os.path.abspath(output_file)), exist_ok=True)
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(html)
        emit_structured_log("info", f"HTML report saved to {output_file}", operation=Operation.WRITE_OUTPUT)
    except Exception as e:
        emit_structured_log("error", f"Error writing to HTML file: {e}", operation=Operation.WRITE_OUTPUT)
        sys.exit(1)


def write_results_to_console(results, agent_name: Optional[str] = None,
                             agent_id: Optional[str] = None,
                             cli_version: Optional[str] = None,
                             judge: Optional[str] = None):
    """Write the response to console."""
    # ANSI color codes
    BOLD = '\033[1m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    CYAN = '\033[96m'
    MAGENTA = '\033[95m'
    ORANGE = '\033[38;5;208m'
    RED = '\033[91m'
    RESET = '\033[0m'

    def _print_evaluated_item(response: str, expected_response: str,
                              evaluators_ran: List[str], item_results: Dict[str, Any],
                              error: Optional[str] = None) -> None:
        """Print the body of a single evaluated item (single-turn prompt or multi-turn turn).

        The item header (Prompt X / Turn X) is printed by the caller; this helper
        prints evaluators, response, expected response, error, and metrics.
        """
        if evaluators_ran:
            print(f"{BOLD}{CYAN}Evaluators:{RESET} {', '.join(evaluators_ran)}")
        if response:
            print(f"{BOLD}{CYAN}Response:{RESET} {response}")
        if expected_response:
            print(f"{BOLD}{YELLOW}Expected Response:{RESET} {expected_response}")
        if error:
            print(f"{BOLD}{RED}Error:{RESET} {error}")

        for eval_name, v in item_results.items():
            if v is None:
                continue
            display_name = pascal_case_to_title(eval_name)
            if eval_name == RELEVANCE:
                color = MAGENTA
            elif eval_name == COHERENCE:
                color = ORANGE
            else:
                color = BLUE
            print(f"{BOLD}{color}{display_name}:{RESET} {json.dumps(v, indent=4)}")

    # Show metadata
    metadata_parts = []
    if agent_name:
        metadata_parts.append(f"Agent Name: {agent_name}")
    if agent_id:
        metadata_parts.append(f"Agent ID: {agent_id}")
    if cli_version:
        metadata_parts.append(f"CLI Version: {cli_version}")
    if judge:
        metadata_parts.append(f"Judge: {judge}")
    if metadata_parts:
        print(f"{BOLD}{CYAN}{' | '.join(metadata_parts)}{RESET}")
        print()

    aggregates = calculate_aggregate_statistics(results)
    if aggregates:
        total_items = aggregates[next(iter(aggregates))].get('total_prompts', len(results))
        if total_items > 1:
            print(f"{BOLD}{BLUE}Aggregate Statistics ({total_items} prompts):{RESET}")
            print(f"{BLUE}{'=' * 60}{RESET}")

            for metric_name, stats in aggregates.items():
                pass_color = GREEN if stats['pass_rate'] >= 80 else YELLOW if stats['pass_rate'] >= 60 else RED
                prompts_evaluated = stats.get('prompts_evaluated', stats['total_evaluated'])
                total_prompts = stats.get('total_prompts', total_items)
                print(f"{BOLD}{CYAN}{metric_name}:{RESET} ({prompts_evaluated}/{total_prompts} prompts)")
                print(f"  Pass Rate: {pass_color}{stats['pass_rate']:.1f}%{RESET} ({stats['pass_count']}/{stats['total_evaluated']} passed)")
                print(f"  Avg Score: {MAGENTA}{stats['avg_score']:.2f}{RESET}")
                if stats.get('threshold') is not None:
                    print(f"  Threshold: {YELLOW}{stats['threshold']}{RESET}")
                print()

            print(f"{BLUE}{'=' * 60}{RESET}")
            print()

    print(f"{BOLD}{BLUE}Individual Results:{RESET}")
    print(f"{BLUE}{'=' * 50}{RESET}")
    for i, result in enumerate(results, 1):
        if result.get("type") == "multi_turn":
            thread_name = result.get("name", "Unnamed Thread")
            summary = result.get("summary", {})
            status = summary.get("overall_status", STATUS_UNKNOWN)
            status_color = GREEN if status == STATUS_PASS else YELLOW if status == STATUS_PARTIAL else RED

            print(f"{BOLD}{MAGENTA}Thread {i}: {thread_name}{RESET}")
            for t_idx, turn in enumerate(result.get("turns", []), 1):
                turn_status = turn.get("status", STATUS_UNKNOWN)
                turn_color = GREEN if turn_status == STATUS_PASS else RED if turn_status in (STATUS_FAIL, STATUS_ERROR) else YELLOW
                print(f"{BOLD}{turn_color}Turn {t_idx}:{RESET} [{turn_status}] {turn.get('prompt', '')}")
                _print_evaluated_item(
                    response=turn.get("response", ""),
                    expected_response=turn.get("expected_response", ""),
                    evaluators_ran=turn.get("evaluators_ran", []),
                    item_results=turn.get("results", {}),
                    error=_format_error_object(turn.get("error")),
                )
            print()
            print(f"{BOLD}{MAGENTA}Thread {i} Summary:{RESET}")
            print(f"  Status: {status_color}{status.upper()}{RESET}")
            print(f"  Turns passed: {status_color}{summary.get('turns_passed', 0)}/{summary.get('turns_total', 0)}{RESET}")
            print(f"{BLUE}{'-' * 30}{RESET}")
        else:
            print(f"{BOLD}{GREEN}Prompt {i}:{RESET} {result['prompt']}")
            _print_evaluated_item(
                response=result.get('response', ''),
                expected_response=result.get('expected_response', ''),
                evaluators_ran=result.get('evaluators_ran', []),
                item_results=result.get('results', {}),
                error=_format_error_object(result.get('error')),
            )
            print(f"{BLUE}{'-' * 30}{RESET}")


def _format_error_object(error_obj: Optional[Dict[str, str]]) -> str:
    """Flatten an ErrorObject ``{code, message}`` to ``"code: message"`` for one-line
    contexts (console summary, CSV cell). Empty string when absent."""
    if not error_obj:
        return ""
    return f"{error_obj['code']}: {error_obj['message']}"


def _as_errored_score(data: dict) -> Optional[Dict[str, Any]]:
    """If ``data`` is an errored entry, return its ErroredScore dict; else None."""
    if data.get("result") == STATUS_ERROR and isinstance(data.get("error"), str):
        errored: Dict[str, Any] = {"result": STATUS_ERROR, "error": data["error"]}
        code = data.get("code")
        if isinstance(code, str) and code:
            errored["code"] = code
        return errored
    return None


# ── Per-evaluator-type valid-shape builders ─────────────────────────
# Each takes a decorated metric dict and returns the schema-compliant valid
# variant. They never see errored entries — _convert_scores_to_schema's loop
# handles ErroredScore dispatch before reaching these.


def _build_eval_score(data: dict, metric_id: str) -> Optional[Dict[str, Any]]:
    """Standard 1-5 score: {score, result, threshold, reason?}. None if no numeric score."""
    score_val = data.get(metric_id)
    if not isinstance(score_val, (int, float)):
        return None
    result = data.get("result")
    if result not in (STATUS_PASS, STATUS_FAIL):
        result = STATUS_PASS if score_val >= data.get("threshold", DEFAULT_PASS_THRESHOLD) else STATUS_FAIL
    out: Dict[str, Any] = {
        "score": score_val,
        "result": result,
        "threshold": data.get("threshold", DEFAULT_PASS_THRESHOLD),
    }
    reason = data.get(f"{metric_id}_reason") or data.get("reason")
    if reason:
        out["reason"] = reason
    return out


def _build_citation_score(data: dict, _metric_id: str) -> Dict[str, Any]:
    count = data.get("citations", 0)
    result = data.get("result")
    if result not in (STATUS_PASS, STATUS_FAIL):
        result = STATUS_PASS if count >= data.get("threshold", 1) else STATUS_FAIL
    out: Dict[str, Any] = {
        "count": count,
        "result": result,
        "threshold": data.get("threshold", 1),
    }
    if "citation_format" in data:
        out["format"] = data["citation_format"]
    return out


def _build_exact_match_score(data: dict, _metric_id: str) -> Dict[str, Any]:
    is_match = data.get("exact_match", 0.0) == 1.0
    return {
        "match": is_match,
        "result": data.get("result", STATUS_PASS if is_match else STATUS_FAIL),
        "reason": data.get("exact_match_reason", ""),
    }


def _build_partial_match_score(data: dict, _metric_id: str) -> Dict[str, Any]:
    return {
        "score": data.get("partial_match", 0.0),
        "result": data.get("result", STATUS_FAIL),
        "threshold": data.get("threshold", 0.5),
        "reason": data.get("partial_match_reason", ""),
    }


def _build_retrieval_query_score(data: dict, metric_id: str) -> Optional[Dict[str, Any]]:
    """Schema-compliant RetrievalQuery score.

    Preserves the diagnostic surface (``diagnostic_code``, ``matched_queries``,
    ``includes_missing``, ``excludes_found``) so JSON / CSV / HTML readers see
    the same per-item detail the console already prints.
    """
    score_val = data.get(metric_id)
    if not isinstance(score_val, (int, float)):
        return None
    out: Dict[str, Any] = {
        "score": score_val,
        "result": data.get("result", STATUS_FAIL),
        "threshold": data.get("threshold", 1.0),
        "diagnostic_code": data.get("diagnostic_code", ""),
    }
    for field in ("reason", "matched_queries", "includes_missing", "excludes_found"):
        if field in data:
            out[field] = data[field]
    return out


def _build_retrieval_result_score(data: dict, metric_id: str) -> Optional[Dict[str, Any]]:
    """Schema-compliant RetrievalResult score.

    Preserves the diagnostic surface (``diagnostic_code``,
    ``results_evaluated``, ``matched_items``, ``missing_items``,
    ``extract_failures``) so per-prompt reports show why an assertion passed
    or failed.
    """
    score_val = data.get(metric_id)
    if not isinstance(score_val, (int, float)):
        return None
    out: Dict[str, Any] = {
        "score": score_val,
        "result": data.get("result", STATUS_FAIL),
        "threshold": data.get("threshold", 1.0),
        "diagnostic_code": data.get("diagnostic_code", ""),
    }
    for field in ("reason", "results_evaluated", "matched_items", "missing_items", "extract_failures"):
        if field in data:
            out[field] = data[field]
    return out


# Internal evaluator name → (schema-output key, valid-shape builder).
# Acts as both an ordered iteration source AND a dict lookup for runtime
# dispatch (anything not present is treated as a custom LLM-judge evaluator).
_SCORE_CONVERTERS: Dict[str, Tuple[str, Any]] = {
    RELEVANCE:          ("relevance",        _build_eval_score),
    COHERENCE:          ("coherence",        _build_eval_score),
    GROUNDEDNESS:       ("groundedness",     _build_eval_score),
    SIMILARITY:         ("similarity",       _build_eval_score),
    TOOL_CALL_ACCURACY: ("toolCallAccuracy", _build_eval_score),
    CITATIONS:          ("citations",        _build_citation_score),
    EXACT_MATCH:        ("exactMatch",       _build_exact_match_score),
    PARTIAL_MATCH:      ("partialMatch",     _build_partial_match_score),
    RETRIEVAL_QUERY:    ("retrievalQuery",   _build_retrieval_query_score),
    RETRIEVAL_RESULT:   ("retrievalResult",  _build_retrieval_result_score),
}


def extract_eval_score(data: dict, metric_id: str) -> Optional[Dict]:
    """Extract a schema-compliant EvalScore from a decorated metric dict.

    Returns ErroredScore for crashes, the standard 1-5 score shape on success,
    or None if no usable numeric score.
    """
    errored = _as_errored_score(data)
    if errored is not None:
        return errored
    return _build_eval_score(data, metric_id)


def _convert_scores_to_schema(results_dict: Dict[str, Any]) -> Dict[str, Any]:
    """Convert raw evaluator results to schema-compliant score objects.

    Each value in results_dict is either a decorated metric dict (valid score)
    or an errored entry ``{result: "error", error}``. Built-in evaluators are
    dispatched through their typed converter in ``_SCORE_CONVERTERS``; any
    other key is treated as a custom LLM-judge score (FR-021) — the schema
    key equals the evaluator's folder name (e.g. ``"professional_tone"``),
    permitted by ``ScoreCollection.additionalProperties``.

    Errored entries pass through unchanged as ErroredScore. Evaluators not
    present in results_dict are omitted from the output.
    """
    scores: Dict[str, Any] = {}
    for eval_key, data in results_dict.items():
        if data is None:
            continue
        schema_key, build_valid_score = _SCORE_CONVERTERS.get(
            eval_key, (eval_key, _build_eval_score)
        )
        errored = _as_errored_score(data)
        if errored is not None:
            scores[schema_key] = errored
            continue
        valid = build_valid_score(data, METRIC_IDS.get(eval_key, eval_key))
        if valid is not None:
            scores[schema_key] = valid
    return scores


def convert_single_item_result_to_output(source: Dict) -> Dict[str, Any]:
    """Convert a single item result — a single-turn item OR one turn inside
    a multi-turn thread — to its schema-compliant output shape.

    Common shape: prompt, expected_response?, response?, evaluators?,
    evaluators_mode?, scores?, status?, error?. Optional fields are emitted
    only when present on the source.
    """
    out: Dict[str, Any] = {"prompt": source.get("prompt", "")}
    for key in ("expected_response", "response", "evaluators", "evaluators_mode"):
        if key in source:
            out[key] = source[key]
    scores = _convert_scores_to_schema(source.get("results", {}))
    if scores:
        out["scores"] = scores
    if "status" in source:
        out["status"] = source["status"]
    if "error" in source:
        out["error"] = source["error"]
    # Internal ``_diagnostics`` container → public ``diagnostics``.
    if source.get("_diagnostics"):
        out["diagnostics"] = source["_diagnostics"]
    # Pass first-class grouping fields and custom metadata through verbatim.
    # Empty collections are treated as absent so JSON/CSV/HTML stay consistent.
    for key in ("tags", "extensions"):
        if source.get(key):
            out[key] = source[key]
    return out


def convert_thread_result_to_output(thread_result: Dict) -> Dict:
    """Convert a multi-turn thread result to a schema-compliant ThreadOutput."""
    output: Dict[str, Any] = {}
    if thread_result.get("name"):
        output["name"] = thread_result["name"]
    if thread_result.get("description"):
        output["description"] = thread_result["description"]
    if thread_result.get("conversation_id"):
        output["conversation_id"] = thread_result["conversation_id"]
    output["turns"] = [convert_single_item_result_to_output(t) for t in thread_result.get("turns", [])]
    if thread_result.get("summary"):
        output["summary"] = thread_result["summary"]
    for key in ("tags", "extensions"):
        if thread_result.get(key):
            output[key] = thread_result[key]
    return output


def convert_result_to_output_item(result: Dict) -> Dict:
    """Top-level dispatch: routes a result dict by item type to the right converter."""
    if result.get("type") == "multi_turn":
        return convert_thread_result_to_output(result)
    return convert_single_item_result_to_output(result)


def write_results_to_json(results: List[Dict], output_file: str, agent_id: Optional[str] = None,
                          default_evaluators: Optional[Dict[str, Any]] = None,
                          agent_name: Optional[str] = None,
                          cli_version: Optional[str] = None,
                          judge: Optional[str] = None,
                          document_extensions: Optional[Dict[str, Any]] = None):
    """Write results to a schema-compliant eval document JSON file.

    Output follows the eval-document.schema.json format:
    {schemaVersion, metadata, default_evaluators?, items: [EvalItem]}
    """
    try:
        try:
            current_version = SchemaVersionManager().get_current_version()
        except Exception:
            current_version = "1.0.0"

        items = [convert_result_to_output_item(r) for r in results]

        metadata: Dict[str, Any] = {
            "evaluatedAt": datetime.now(timezone.utc).isoformat(),
        }
        if agent_id:
            metadata["agentId"] = agent_id
        if agent_name:
            metadata["agentName"] = agent_name
        if cli_version:
            metadata["cliVersion"] = cli_version
        if judge:
            metadata["judge"] = judge
        # Echo document-level custom metadata through verbatim.
        if document_extensions:
            metadata["extensions"] = document_extensions

        output_data: Dict[str, Any] = {
            "schemaVersion": current_version,
            "metadata": metadata,
        }

        if default_evaluators is not None:
            output_data["default_evaluators"] = default_evaluators

        output_data["items"] = items

        os.makedirs(os.path.dirname(os.path.abspath(output_file)), exist_ok=True)
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(output_data, f, indent=2, ensure_ascii=False)
        emit_structured_log("info", f"Results saved to {output_file}", operation=Operation.WRITE_OUTPUT)
    except Exception as e:
        emit_structured_log("error", f"Error writing to JSON file: {e}", operation=Operation.WRITE_OUTPUT)
        sys.exit(1)


def _results_to_csv_json(results_dict: Dict) -> str:
    """Serialize evaluator results dict to a CSV-safe JSON string.

    Skips None (crashed/skipped evaluators). Results are dicts produced
    by _decorate_metric.
    """
    if not results_dict:
        return ""
    non_null = {k: v for k, v in results_dict.items() if v is not None}
    return json.dumps(non_null) if non_null else ""


def _diagnostics_to_csv_json(diagnostics: Optional[Dict]) -> str:
    """Serialize the diagnostics container to a CSV-safe JSON string.

    Returns "" when diagnostics are absent. The whole container (all
    categories, currently just ``retrieval_executions``) is serialized so the
    CSV column round-trips the same data as the JSON output.
    """
    if not diagnostics:
        return ""
    return json.dumps(diagnostics, ensure_ascii=False)


def _metadata_to_csv_json(value: Optional[Any]) -> str:
    """Serialize a pass-through metadata value (``tags`` list or ``extensions``
    object) to a CSV-safe JSON string.

    Returns "" when the value is absent/empty so the column round-trips the same
    verbatim value as the JSON output.
    """
    if not value:
        return ""
    return json.dumps(value, ensure_ascii=False)


def write_results_to_csv(results: List[Dict], output_file: str,
                         agent_name: Optional[str] = None, agent_id: Optional[str] = None,
                         cli_version: Optional[str] = None, judge: Optional[str] = None):
    """Write results to CSV file."""
    try:
        os.makedirs(os.path.dirname(os.path.abspath(output_file)), exist_ok=True)
        with open(output_file, 'w', newline='', encoding='utf-8') as f:
            if results:
                metadata_parts = []
                if agent_name:
                    metadata_parts.append(f"Agent Name: {agent_name}")
                if agent_id:
                    metadata_parts.append(f"Agent ID: {agent_id}")
                if cli_version:
                    metadata_parts.append(f"CLI Version: {cli_version}")
                if judge:
                    metadata_parts.append(f"Judge: {judge}")
                if metadata_parts:
                    f.write(f"# {' | '.join(metadata_parts)}\n")

                aggregates = calculate_aggregate_statistics(results)
                if aggregates:
                    total_items = aggregates[next(iter(aggregates))].get('total_prompts', len(results))
                    if total_items > 1:
                        f.write("# AGGREGATE STATISTICS\n")
                        f.write("Metric,Prompts Evaluated,Total Prompts,Pass Rate (%),Passed,Failed,Errored,Avg Score,Threshold\n")
                        for metric_name, stats in aggregates.items():
                            threshold_val = stats.get('threshold')
                            threshold_str = "N/A" if threshold_val is None else str(threshold_val)
                            prompts_evaluated = stats.get('prompts_evaluated', stats['total_evaluated'])
                            total_prompts = stats.get('total_prompts', total_items)
                            error_count = stats.get('error_count', 0)
                            f.write(f"{metric_name},{prompts_evaluated},{total_prompts},{stats['pass_rate']:.1f},{stats['pass_count']},{stats['fail_count']},{error_count},{stats['avg_score']:.2f},{threshold_str}\n")
                        f.write("\n# INDIVIDUAL RESULTS\n")

                single_turn_rows = []
                multi_turn_rows = []
                for result in results:
                    if result.get("type") == "multi_turn":
                        thread_name = result.get("name", "")
                        for turn_idx, turn in enumerate(result.get("turns", [])):
                            multi_turn_rows.append({
                                "thread_name": thread_name,
                                "turn_index": turn_idx + 1,
                                "prompt": turn.get("prompt", ""),
                                "response": turn.get("response", ""),
                                "expected_response": turn.get("expected_response", ""),
                                "status": turn.get("status", ""),
                                "error": _format_error_object(turn.get("error")),
                                "scores": _results_to_csv_json(turn.get("results", {})),
                                "diagnostics": _diagnostics_to_csv_json(turn.get("_diagnostics")),
                                "tags": _metadata_to_csv_json(turn.get("tags")),
                                "extensions": _metadata_to_csv_json(turn.get("extensions")),
                            })
                        summary = result.get("summary", {})
                        multi_turn_rows.append({
                            "thread_name": thread_name,
                            "turn_index": "summary",
                            "prompt": "",
                            "response": "",
                            "expected_response": "",
                            "status": summary.get("overall_status", ""),
                            "scores": f"{summary.get('turns_passed', 0)}/{summary.get('turns_total', 0)} turns passed",
                            "tags": _metadata_to_csv_json(result.get("tags")),
                            "extensions": _metadata_to_csv_json(result.get("extensions")),
                        })
                    else:
                        exclude_keys = {'evaluators_ran', 'evaluators', 'evaluators_mode', '_enhanced_response', 'results', '_diagnostics', 'extensions', 'tags'}
                        row = {k: v for k, v in result.items() if k not in exclude_keys}
                        if "error" in row:
                            row["error"] = _format_error_object(row["error"])
                        if "results" in result:
                            row["scores"] = _results_to_csv_json(result["results"])
                        if result.get("_diagnostics"):
                            row["diagnostics"] = _diagnostics_to_csv_json(result["_diagnostics"])
                        if result.get("tags"):
                            row["tags"] = _metadata_to_csv_json(result["tags"])
                        if result.get("extensions"):
                            row["extensions"] = _metadata_to_csv_json(result["extensions"])
                        single_turn_rows.append(row)

                if single_turn_rows:
                    if multi_turn_rows:
                        f.write("# SINGLE-TURN RESULTS\n")
                    fieldnames = list(single_turn_rows[0].keys())
                    for row in single_turn_rows:
                        for k in row:
                            if k not in fieldnames:
                                fieldnames.append(k)
                    writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
                    writer.writeheader()
                    writer.writerows(single_turn_rows)

                if multi_turn_rows:
                    if single_turn_rows:
                        f.write("\n")
                    f.write("# MULTI-TURN RESULTS\n")
                    fieldnames = ["thread_name", "turn_index", "prompt", "response", "expected_response", "status", "error", "scores", "diagnostics", "tags", "extensions"]
                    writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
                    writer.writeheader()
                    writer.writerows(multi_turn_rows)
            emit_structured_log("info", f"Results saved to {output_file}", operation=Operation.WRITE_OUTPUT)
    except Exception as e:
        emit_structured_log("error", f"Error writing to CSV file: {e}", operation=Operation.WRITE_OUTPUT)
        sys.exit(1)


def output_results(results: List[Dict], config: RunConfig, default_evaluators: Optional[Dict[str, Any]] = None,
                    agent_name: Optional[str] = None, cli_version: Optional[str] = None,
                    judge: Optional[str] = None, document_extensions: Optional[Dict[str, Any]] = None):
    """Output results based on specified format."""
    metadata_kwargs = dict(
        agent_name=agent_name,
        agent_id=config.m365_agent_id,
        cli_version=cli_version,
        judge=judge,
    )
    if config.output:
        output_lower = config.output.lower()
        if output_lower.endswith('.json'):
            write_results_to_json(results, config.output, default_evaluators=default_evaluators,
                                  document_extensions=document_extensions, **metadata_kwargs)
        elif output_lower.endswith('.csv'):
            write_results_to_csv(results, config.output, **metadata_kwargs)
        elif output_lower.endswith('.html'):
            write_results_to_html(results, config.output, **metadata_kwargs)
            abs_path = os.path.abspath(config.output)
            webbrowser.open(f'file://{abs_path}')
        else:
            write_results_to_json(results, config.output, default_evaluators=default_evaluators,
                                  document_extensions=document_extensions, **metadata_kwargs)
    else:
        write_results_to_console(results, **metadata_kwargs)
