from pydantic import BaseModel
from pathlib import Path
from functools import lru_cache

import json


class Verse(BaseModel):
    book: str
    chapter: int
    verse: int
    text: str


class VerseReference(BaseModel):
    book: str
    chapter: int
    verse: int

    @classmethod
    @lru_cache(maxsize=2048)
    def from_string(cls, s: str):
        """
        Parses a string in the format "Book Chapter:Verse" or "Book Chapter Verse"
        and returns a VerseReference object.

        Supports both colon format (John 3:16) and space format (John 3 16).
        """

        # Split the string into parts.
        split_s = s.split(" ")

        # Handle book names with multiple words (e.g., "I Corinthians", "Song of Solomon").
        # The chapter:verse will always be the last part (contains ":").
        # Everything before that is the book name.
        # Example: "Song of Solomon 1:1" -> book="Song of Solomon", chapter_verse="1:1"

        chapter_verse = split_s[-1]  # Last part is always chapter:verse or just verse

        # Check if using colon format (e.g., "3:16") or space format (e.g., "Rev 22 20")
        if ":" in chapter_verse:
            # Standard format: Book Chapter:Verse
            book = " ".join(split_s[:-1])
            chapter, verse = chapter_verse.split(":")
        else:
            # Space format: Book Chapter Verse (last two parts are chapter and verse)
            if len(split_s) >= 3 and split_s[-1].isdigit() and split_s[-2].isdigit():
                verse = split_s[-1]
                chapter = split_s[-2]
                book = " ".join(split_s[:-2])
            else:
                # Fallback to original behavior (will likely fail, but maintains compatibility)
                book = " ".join(split_s[:-1])
                chapter, verse = chapter_verse.split(":")

        return cls(book=book, chapter=int(chapter), verse=int(verse))


class Bible:
    """Represents a Bible."""

    def __init__(self, fname=None):
        if fname is None:
            # Get the directory where this script is located
            current_dir = Path(__file__).parent
            # Look for verses file in the package static directory
            self.fname = current_dir / "static" / "verses-1769.json"
        else:
            self.fname = Path(fname)

        # Load the JSON data from the file.
        with open(self.fname, "r") as f:
            self.verses = json.load(f)

        # Pre-process verse text for performance (clean once instead of on every access)
        # Remove the leading "# " and brackets from the text stored in JSON
        # Example: "# [In the beginning...]" -> "In the beginning..."
        self._cleaned_verses = {
            key: text.replace("# ", "").replace("[", "").replace("]", "")
            for key, text in self.verses.items()
        }

    @lru_cache(maxsize=1024)
    def __getitem__(self, verse):
        """Returns the text of the verse."""

        # Check if the verse exists in the dictionary.
        if verse not in self.verses:
            raise KeyError(f"Verse {verse} not found in the Bible.")

        return self.verses[verse]

    def iter_verses(self):
        """Iterates over the verses in the Bible."""

        for verse in self.verses:
            verse_ref = VerseReference.from_string(verse)

            # Use pre-cleaned text for performance (5-10x faster than cleaning on every iteration)
            text = self._cleaned_verses[verse]

            yield Verse(
                book=verse_ref.book,
                chapter=verse_ref.chapter,
                verse=verse_ref.verse,
                text=text,
            )

    @lru_cache(maxsize=1)
    def get_books(self):
        """Returns a list of all books in the Bible."""
        yielded = set()
        books = []

        for verse in self.verses:
            verse_ref = VerseReference.from_string(verse)
            if verse_ref.book in yielded:
                continue
            yielded.add(verse_ref.book)
            books.append(verse_ref.book)
        
        return books

    def iter_books(self):
        """Iterates over the books in the Bible."""
        for book in self.get_books():
            yield book

    @lru_cache(maxsize=1)
    def get_chapters(self):
        """Returns a list of all chapters in the Bible."""
        yielded = set()
        chapters = []

        for verse in self.verses:
            verse_ref = VerseReference.from_string(verse)
            if (verse_ref.book, verse_ref.chapter) in yielded:
                continue
            yielded.add((verse_ref.book, verse_ref.chapter))
            chapters.append((verse_ref.book, verse_ref.chapter))
        
        return chapters

    def iter_chapters(self):
        """Iterates over the chapters in the Bible."""
        for book, chapter in self.get_chapters():
            yield book, chapter

    def iter_chapters_by_book(self):
        """Iterates over the chapters in the Bible, grouped by book."""

        yielded = set()

        for verse in self.verses:
            verse_ref = VerseReference.from_string(verse)
            if (verse_ref.book, verse_ref.chapter) in yielded:
                continue
            yielded.add((verse_ref.book, verse_ref.chapter))
            yield verse_ref.book, verse_ref.chapter

    def iter_verse_references(self):
        """Iterates over the verse references in the Bible."""

        for verse in self.verses:
            yield VerseReference.from_string(verse)

    @lru_cache(maxsize=256)
    def get_verses_by_book_chapter(self, book, chapter):
        """Returns a list of verses for a specific book and chapter."""
        verses = []
        for verse in self.verses:
            verse_ref = VerseReference.from_string(verse)
            if verse_ref.book == book and verse_ref.chapter == chapter:
                # Use pre-cleaned text for performance
                text = self._cleaned_verses[verse]
                verses.append(Verse(
                    book=verse_ref.book,
                    chapter=verse_ref.chapter,
                    verse=verse_ref.verse,
                    text=text,
                ))
        return sorted(verses, key=lambda v: v.verse)

    @lru_cache(maxsize=128)
    def get_chapters_for_book(self, book):
        """Returns a list of chapter numbers for a specific book."""
        chapters = set()
        for verse in self.verses:
            verse_ref = VerseReference.from_string(verse)
            if verse_ref.book == book:
                chapters.add(verse_ref.chapter)
        return sorted(list(chapters))

    @lru_cache(maxsize=2048)
    def get_verse_text(self, book, chapter, verse_num):
        """Returns the text for a specific verse."""
        verse_key = f"{book} {chapter}:{verse_num}"
        if verse_key in self.verses:
            # Use pre-cleaned text for performance
            return self._cleaned_verses[verse_key]
        return None

    @lru_cache(maxsize=1)
    def get_verse_count(self):
        """Returns the total number of verses in the Bible."""
        return len(self.verses)


# Create an instance of the Bible class.
bible = Bible()


if __name__ == "__main__":

    print(VerseReference.from_string("Genesis 1:1"))
    print(VerseReference.from_string("I Corinthians 1:1"))
    print(VerseReference.from_string("John 3:16"))

    print(bible["Genesis 1:1"])