"""Docdiff module for docdiff."""

from __future__ import annotations

import argparse
import atexit
import json
import logging
import platform
import subprocess
import time
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import Any, Final

# Configuration file path
CONFIG_FILE: Final[Path] = Path.home() / ".pytola" / "docdiff.json"

logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)


@dataclass
class DocDiffConfig:
    """Document comparison configuration."""

    DOC_DIFF_TITLE: str = "Comparison Result"
    OUTPUT_DIR: str = str(Path.home())  # Use current directory if empty
    COMPARE_MODE: str = "original"  # Options: original, revised
    SHOW_CHANGES: bool = True
    TRACK_REVISIONS: bool = True

    def __init__(self) -> None:
        if CONFIG_FILE.exists():
            logger.info("Loading configuration from %s", CONFIG_FILE)
            config_data = json.loads(CONFIG_FILE.read_text())
            # Update configuration items, keeping defaults as fallback
            for key, value in config_data.items():
                if hasattr(self, key):
                    setattr(self, key, value)
        else:
            logger.info("Using default configuration")

    def save(self) -> None:
        """Save configuration."""
        CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
        CONFIG_FILE.write_text(json.dumps(vars(self), indent=4))


conf = DocDiffConfig()
atexit.register(conf.save)


@dataclass(frozen=True)
class DiffDocCommand:
    """Document comparison command."""

    old_doc: Path
    new_doc: Path
    output_path: Path | None = None

    def run(self) -> None:
        """Run the document comparison command."""
        if platform.system() != "Windows":
            logger.error("This tool is only available on Windows.")
            return
        if not self.old_doc.exists():
            logger.error(f"Old file does not exist: {self.old_doc}")
            raise FileNotFoundError(f"Old file does not exist: {self.old_doc}")

        if not self.new_doc.exists():
            logger.error(f"New file does not exist: {self.new_doc}")
            raise FileNotFoundError(f"New file does not exist: {self.new_doc}")

        if not self.validate_files:
            logger.error("Invalid file paths or extensions")
            raise ValueError("Invalid file paths or extensions")

        if self.word_app is None:
            logger.error("Word application is not available")
            raise RuntimeError("Word application is not available")

        if self.compare_data is None:
            raise RuntimeError("Comparison failed")

        self.output.parent.mkdir(parents=True, exist_ok=True)
        try:
            self.compare_data.SaveAs2(str(self.output))
            self.compare_data.Close()
        except Exception as e:
            logger.exception(f"Comparison failed: {e}")
        else:
            logger.info(f"Comparison completed. Saved to: {self.output}")
        finally:
            try:
                self.word_app.Documents.Close(SaveChanges=False)
            except Exception:
                logger.exception("Close document failed!")
            else:
                self.word_app.Quit()

            try:
                subprocess.run(
                    ["taskkill", "/f", "/t", "/im", "WINWORD.EXE"],
                    check=False,
                )
            except Exception:
                logger.exception("Taskkill failed!")
            else:
                logger.info("Taskkill completed successfully")

    @cached_property
    def word_app(self) -> Any:
        """Start the Word application for document comparison."""
        try:
            import win32com.client as win32  # type: ignore
        except ImportError:
            logger.exception("win32com.client is not installed, exiting.")
            raise
        else:
            logger.info("Started Word application")
            app = win32.gencache.EnsureDispatch("Word.Application")  # type: ignore
            app.Visible = False
            app.DisplayAlerts = False

            try:
                app.Options.TrackRevisions = conf.TRACK_REVISIONS
            except AttributeError:
                logger.warning(
                    "TrackRevisions option not available in this Word version",
                )

            return app

    @cached_property
    def validate_files(self) -> bool:
        """Validate the input files for comparison."""
        return all(
            [
                self.old_doc.exists(),
                self.new_doc.exists(),
                self.old_doc.suffix.lower() in {".doc", ".docx"},
                self.new_doc.suffix.lower() in {".doc", ".docx"},
            ]
        )

    @cached_property
    def compare_data(self) -> Any:
        """Perform document comparison using Word application."""
        try:
            compared = self.word_app.CompareDocuments(
                self.old_doc_data,
                self.new_doc_data,
                0,
                2 if conf.COMPARE_MODE == "revised" else 0,
                True,
            )
        except Exception as e:
            logger.exception(f"Comparison failed: {e}")
            return None
        else:
            if compared:
                logger.info("Comparison completed successfully")
                compared.ShowRevisions = conf.SHOW_CHANGES
                return compared
            return None

    @cached_property
    def old_doc_data(self) -> Any:
        """Open the old document for comparison."""
        logger.info(f"Opening old file: {self.old_doc}")
        return self.word_app.Documents.Open(str(self.old_doc.resolve()))

    @cached_property
    def new_doc_data(self) -> Any:
        """Open the new document for comparison."""
        logger.info(f"Opening new file: {self.new_doc}")
        return self.word_app.Documents.Open(str(self.new_doc.resolve()))

    @cached_property
    def output(self) -> Path:
        """Determine the output directory for the comparison result."""
        output_filename = f"{conf.DOC_DIFF_TITLE}@{time.strftime('%Y%m%d_%H_%M_%S')}.docx"

        if self.output_path is None:
            output_dir = Path(conf.OUTPUT_DIR) if conf.OUTPUT_DIR else self.new_doc.parent
            return output_dir / output_filename

        if self.output_path.is_dir():
            return self.output_path / output_filename
        if self.output_path.is_file():
            return self.output_path
        msg = f"Invalid output path: {self.output_path}"
        raise ValueError(msg)


def parse_args():
    """Parse command line arguments for document comparison."""
    parser = argparse.ArgumentParser(description="Compare two doc/docx files.")
    parser.add_argument(
        "files",
        nargs=2,
        help="Two input files to compare (old_file new_file)",
    )
    parser.add_argument(
        "-o",
        "--output",
        dest="output",
        default=".",
        help="Output file path",
    )
    parser.add_argument("--title", help="Title for the comparison result")
    parser.add_argument(
        "--show-changes",
        action="store_true",
        help="Show changes in the comparison",
    )
    parser.add_argument(
        "--hide-changes",
        action="store_true",
        help="Hide changes in the comparison",
    )
    parser.add_argument(
        "--compare-mode",
        choices=["original", "revised"],
        help="Compare mode: original or revised",
    )
    parser.add_argument("--output-dir", help="Output directory for the result file")

    args = parser.parse_args()

    # Update configuration from command line arguments
    if args.title:
        conf.DOC_DIFF_TITLE = args.title
    if args.show_changes:
        conf.SHOW_CHANGES = True
    if args.hide_changes:
        conf.SHOW_CHANGES = False
    if args.compare_mode:
        conf.COMPARE_MODE = args.compare_mode
    if args.output_dir:
        conf.OUTPUT_DIR = args.output_dir

    return args


def main() -> None:
    """Compare two doc/docx files."""
    args = parse_args()

    DiffDocCommand(
        Path(args.files[0]),
        Path(args.files[1]),
        Path(args.output),
    ).run()
