import os
from datetime import datetime
from pathlib import Path

from rich.console import Console
from rich.progress import (
    BarColumn,
    Progress,
    SpinnerColumn,
    TaskProgressColumn,
    TextColumn,
)

from c2m_by_nery.core.config import ConfigManager
from c2m_by_nery.core.converter import (
    convert_file_to_markdown,
    generate_frontmatter,
)
from c2m_by_nery.core.git_metadata import get_git_metadata
from c2m_by_nery.core.output import (
    create_snapshot_dir,
    create_snapshot_dir_for_file,
    create_zip_archive,
    get_output_file_path,
    write_report,
)
from c2m_by_nery.core.scanner import Scanner
from c2m_by_nery.core.tokenizer import count_tokens, segment_content


def _user_friendly_error(exc: Exception) -> str:
    err_str = str(exc).lower()
    type_name = type(exc).__name__.lower()
    if "permission" in err_str or "permissionerror" in type_name:
        return "Permission denied (cannot read file)"
    if "filenotfounderror" in type_name:
        return "File not found"
    if "unicodedecodeerror" in type_name or "unicodedecode" in err_str or "encoding" in err_str:
        return "Encoding error (corrupted or binary file)"
    if "conversion" in err_str or "docling" in err_str:
        return "Document conversion failed"
    return str(exc)


class _NullProgress:
    def add_task(self, *args, **kwargs):
        return 0

    def update(self, *args, **kwargs):
        pass

    def advance(self, *args, **kwargs):
        pass

    def __enter__(self):
        return self

    def __exit__(self, *args):
        pass


SINGLE_FILE_OUTPUT_NAME = "output.md"
PART_SEPARATOR = "\n\n<!-- Part {n}/{total} -->\n\n"


def run_single_file_conversion(
    file_path: Path,
    config: ConfigManager,
    token_limit: int,
    *,
    console: Console | None = None,
    verbose: bool = False,
    quiet: bool = False,
    zip_output: bool = False,
    flat_output: bool | None = None,
    single_file: bool | None = None,
    frontmatter: bool = True,
) -> dict:
    file_path = file_path.resolve()
    use_single = single_file if single_file is not None else config.single_file
    _devnull = None
    if quiet:
        _devnull = open(os.devnull, "w")
        console = Console(file=_devnull)
    else:
        console = console or Console()
    try:
        git_meta = get_git_metadata(file_path.parent)
        snapshot_dir, timestamp = create_snapshot_dir_for_file(
            file_path, config.output_path
        )

        start_time = datetime.now()
        report = {
            "timestamp": timestamp,
            "root": str(file_path),
            "output_path": config.output_path,
            "token_limit": token_limit,
            "files_scanned": 1,
            "files_processed": 0,
            "files_written": 0,
            "total_tokens": 0,
            "segments_created": 0,
            "errors": [],
            "files": [],
        }

        progress_columns = [
            SpinnerColumn(),
            TextColumn("[bold blue]{task.description}"),
            BarColumn(bar_width=40),
            TaskProgressColumn(),
            TextColumn("[dim]{task.fields[detail]}"),
        ]
        use_progress = not quiet
        rel_str = file_path.name

        with (
            Progress(*progress_columns, console=console)
            if use_progress
            else _NullProgress()
        ) as progress:
            if use_progress:
                progress.add_task("Converting", total=1, detail=rel_str[:50])

            try:
                markdown = convert_file_to_markdown(file_path)
            except Exception as e:
                report["errors"].append({"file": str(file_path), "error": str(e)})
                report["files"].append({
                    "file": rel_str,
                    "tokens": 0,
                    "status": "error",
                    "error": _user_friendly_error(e),
                })
                report["elapsed_seconds"] = 0
                report["error_count"] = len(report["errors"])
                write_report(snapshot_dir, report)
                return report

            segments = segment_content(markdown, token_limit)
            if not segments:
                segments = [""]

            if use_single:
                parts: list[str] = []
                total_parts = len(segments)
                for i, seg in enumerate(segments):
                    if i > 0:
                        parts.append(PART_SEPARATOR.format(n=i + 1, total=total_parts))
                    parts.append(seg)
                single_content = "".join(parts)
                out_path = snapshot_dir / SINGLE_FILE_OUTPUT_NAME
                out_path.write_text(single_content, encoding="utf-8")
                report["files_written"] = 1
                report["segments_created"] = len(segments)
                report["total_tokens"] = count_tokens(markdown)
                report["files_processed"] = 1
                report["files"].append({
                    "file": rel_str,
                    "tokens": report["total_tokens"],
                    "status": "ok",
                    "segments": len(segments),
                })
            else:
                file_tokens = 0
                for part, segment in enumerate(segments):
                    fm = generate_frontmatter(
                        rel_str,
                        file_path,
                        git_meta,
                        part=part if len(segments) > 1 else None,
                        total_parts=len(segments) if len(segments) > 1 else None,
                        include_frontmatter=frontmatter,
                    )
                    full_content = fm + segment
                    if len(segments) > 1:
                        out_name = f"{file_path.stem}_part{part + 1}.md"
                    else:
                        out_name = f"{file_path.stem}.md"
                    out_path = snapshot_dir / out_name
                    out_path.write_text(full_content, encoding="utf-8")
                    seg_tokens = count_tokens(segment)
                    report["total_tokens"] += seg_tokens
                    file_tokens += seg_tokens
                    report["segments_created"] += 1
                report["files_processed"] = 1
                report["files_written"] = 1
                report["files"].append({
                    "file": rel_str,
                    "tokens": file_tokens,
                    "status": "ok",
                    "segments": len(segments),
                })

            if use_progress:
                progress.advance(0)

        elapsed = (datetime.now() - start_time).total_seconds()
        report["elapsed_seconds"] = round(elapsed, 2)
        report["error_count"] = len(report["errors"])

        write_report(snapshot_dir, report)

        if zip_output:
            output_base = file_path.parent / config.output_path
            zip_path = output_base / f"{timestamp}.zip"
            create_zip_archive(snapshot_dir, zip_path)
            report["zip_path"] = str(zip_path)

        return report
    finally:
        if _devnull is not None:
            _devnull.close()


def run_conversion(
    root: Path,
    config: ConfigManager,
    token_limit: int,
    *,
    console: Console | None = None,
    verbose: bool = False,
    quiet: bool = False,
    zip_output: bool = False,
    flat_output: bool | None = None,
    single_file: bool | None = None,
    frontmatter: bool = True,
) -> dict:
    use_flat = flat_output if flat_output is not None else config.flat_output
    use_single = single_file if single_file is not None else config.single_file
    _devnull = None
    if quiet:
        _devnull = open(os.devnull, "w")
        console = Console(file=_devnull)
    else:
        console = console or Console()
    try:
        git_meta = get_git_metadata(root)
        snapshot_dir, timestamp = create_snapshot_dir(root, config.output_path)

        scanner = Scanner(root, config)
        files = scanner.scan()

        start_time = datetime.now()
        report = {
            "timestamp": timestamp,
            "root": str(root),
            "output_path": config.output_path,
            "token_limit": token_limit,
            "files_scanned": len(files),
            "files_processed": 0,
            "files_written": 0,
            "total_tokens": 0,
            "segments_created": 0,
            "errors": [],
            "files": [],
        }

        progress_columns = [
            SpinnerColumn(),
            TextColumn("[bold blue]{task.description}"),
            BarColumn(bar_width=40),
            TaskProgressColumn(),
            TextColumn("[dim]{task.fields[detail]}"),
        ]
        use_progress = not quiet

        collected: list[str] = []

        with (
            Progress(*progress_columns, console=console)
            if use_progress
            else _NullProgress()
        ) as progress:
            if use_progress:
                task = progress.add_task(
                    "Converting",
                    total=len(files),
                    detail="0 files",
                )

            for filepath in files:
                try:
                    rel_path = filepath.relative_to(root)
                    rel_str = str(rel_path).replace("\\", "/")
                    if use_progress:
                        progress.update(task, detail=rel_str[:50])

                    markdown = convert_file_to_markdown(filepath)

                    if use_single:
                        fm = generate_frontmatter(
                            rel_str, filepath, git_meta, part=None,
                            include_frontmatter=frontmatter,
                        )
                        collected.append(fm + markdown)
                        file_tokens = count_tokens(markdown)
                        report["total_tokens"] += file_tokens
                        report["files_processed"] += 1
                        report["files"].append({
                            "file": rel_str,
                            "tokens": file_tokens,
                            "status": "ok",
                            "segments": 1,
                        })
                    else:
                        segments = segment_content(markdown, token_limit)
                        if not segments:
                            segments = [""]

                        file_tokens = 0
                        for part, segment in enumerate(segments):
                            fm = generate_frontmatter(
                                rel_str,
                                filepath,
                                git_meta,
                                part=part if len(segments) > 1 else None,
                                total_parts=len(segments) if len(segments) > 1 else None,
                                include_frontmatter=frontmatter,
                            )
                            full_content = fm + segment

                            out_name = rel_path.stem
                            if len(segments) > 1:
                                out_name = f"{rel_path.stem}_part{part + 1}"
                            out_name = f"{out_name}.md"

                            out_path = get_output_file_path(
                                snapshot_dir, rel_path, out_name, flat=use_flat
                            )
                            out_path.write_text(full_content, encoding="utf-8")

                            seg_tokens = count_tokens(segment)
                            report["total_tokens"] += seg_tokens
                            file_tokens += seg_tokens
                            report["segments_created"] += 1

                        report["files_processed"] += 1
                        report["files_written"] += 1
                        report["files"].append({
                            "file": rel_str,
                            "tokens": file_tokens,
                            "status": "ok",
                            "segments": len(segments),
                        })

                except Exception as e:
                    friendly = _user_friendly_error(e)
                    report["errors"].append({"file": str(filepath), "error": str(e)})
                    report["files"].append({
                        "file": str(filepath.relative_to(root)).replace("\\", "/"),
                        "tokens": 0,
                        "status": "error",
                        "error": friendly,
                    })

                if use_progress:
                    progress.advance(task)
                    progress.update(task, detail=f"{report['files_processed']} files")

        if use_single and collected:
            combined = "\n\n---\n\n".join(collected)
            final_segments = segment_content(combined, token_limit)
            if not final_segments:
                final_segments = [combined]
            total_parts = len(final_segments)
            parts: list[str] = []
            for i, seg in enumerate(final_segments):
                if i > 0:
                    parts.append(PART_SEPARATOR.format(n=i + 1, total=total_parts))
                parts.append(seg)
            single_content = "".join(parts)
            out_path = snapshot_dir / SINGLE_FILE_OUTPUT_NAME
            out_path.write_text(single_content, encoding="utf-8")
            report["files_written"] = 1
            report["segments_created"] = len(final_segments)

        elapsed = (datetime.now() - start_time).total_seconds()
        report["elapsed_seconds"] = round(elapsed, 2)
        report["error_count"] = len(report["errors"])

        write_report(snapshot_dir, report)

        if zip_output:
            output_base = root / config.output_path
            zip_path = output_base / f"{timestamp}.zip"
            create_zip_archive(snapshot_dir, zip_path)
            report["zip_path"] = str(zip_path)

        return report
    finally:
        if _devnull is not None:
            _devnull.close()
