import flet as ft
import json
import requests
import yaml
import os
import csv
import logging
from typing import Dict, List, Any
from result import Ok

# Set up logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("flashcard-creator")


class FlashcardCreator:
    def __init__(self):
        # Anki configuration
        from anki import AnkiConnect
        self.anki = AnkiConnect()

        # File picker
        self.file_picker = ft.FilePicker()

        # LiteLLM configuration
        self.llm_provider = "openai"
        self.llm_model = "gpt-3.5-turbo"
        self.base_url = ""
        self.api_key = ""
        self.load_settings()

        # Languages
        self.source_language = "English"
        self.target_language = "German"
        self.languages = [
            "English",
            "German",
            "French",
            "Spanish",
            "Italian",
            "Chinese",
            "Japanese",
            "Russian",
        ]

        # Flashcards
        self.flashcards = {}

    def load_settings(self):
        settings_path = os.path.join(
            os.path.expanduser("~"), ".flashcard_creator", "settings.json"
        )
        settings = {}
        if os.path.exists(settings_path):
            with open(settings_path, "r") as f:
                try:
                    settings = json.load(f)
                    print(settings)
                    self.llm_provider = settings.get("provider", self.llm_provider)
                    self.llm_model = settings.get("model", self.llm_model)
                    self.base_url = settings.get("base_url", self.base_url)
                    # Load provider-specific settings
                    provider_settings = settings.get(self.llm_provider, {})
                    self.llm_model = provider_settings.get("model", self.llm_model)
                    self.base_url = provider_settings.get("base_url", self.base_url)
                    self.api_key = provider_settings.get("api_key", self.api_key)
                except:
                    pass

    def build_ui(self, page: ft.Page):
        page.title = "Flashcard Creator"
        page.theme_mode = ft.ThemeMode.LIGHT

        # Language selection dropdowns
        self.source_dropdown = ft.Dropdown(
            label="Source Language",
            options=[ft.dropdown.Option(lang) for lang in self.languages],
            value=self.source_language,
            width=200,
            on_change=self.on_source_language_change,
        )

        self.target_dropdown = ft.Dropdown(
            label="Target Language",
            options=[ft.dropdown.Option(lang) for lang in self.languages],
            value=self.target_language,
            width=200,
            on_change=self.on_target_language_change,
        )

        # Text input for content to process
        self.input_text = ft.TextField(
            label="Input Text", multiline=True, min_lines=5, max_lines=15, width=600
        )

        # Process button
        self.process_button = ft.ElevatedButton(
            text="Process Text", on_click=self.on_process_text
        )

        # Settings button
        self.settings_button = ft.ElevatedButton(
            text="Settings", on_click=self.open_settings
        )

        # Stack name and description
        self.stack_name = ft.TextField(label="Stack Name", width=300)

        self.stack_description = ft.TextField(
            label="Stack Description",
            multiline=True,
            min_lines=2,
            max_lines=4,
            width=600,
        )

        # Action buttons
        self.add_to_anki_button = ft.ElevatedButton(
            text="Add to Anki", on_click=self.add_to_anki
        )

        self.export_yaml_button = ft.ElevatedButton(
            text="Export as YAML", on_click=self.export_as_yaml
        )

        self.export_csv_button = ft.ElevatedButton(
            text="Export as CSV", on_click=self.export_as_csv
        )

        # Flashcard data table - make it expand to fill available width
        self.flashcard_table = ft.DataTable(
            columns=[
                ft.DataColumn(ft.Text("Front")),
                ft.DataColumn(ft.Text("Back")),
                ft.DataColumn(ft.Text("Actions")),
            ],
            rows=[],
            expand=True,  # Make table expand to fill available width
            column_spacing=10,  # Space between columns
            horizontal_lines=ft.border.BorderSide(1, ft.colors.OUTLINE),
            vertical_lines=ft.border.BorderSide(1, ft.colors.OUTLINE),
            width=page.width,  # Make table as wide as the window
        )

        # Wrap the table in a Container with specific height to ensure visibility
        flashcard_table_container = ft.Container(
            content=ft.Column(
                expand=True,
                controls=[self.flashcard_table],
                scroll=ft.ScrollMode.ALWAYS,
            ),
            padding=10,
            expand=True,
            # height=400,
            border=ft.border.all(1, ft.colors.OUTLINE),
            border_radius=5,
            width=page.width,
        )

        # Add file picker to page overlay
        page.overlay.append(self.file_picker)

        # Create a scrollable column for the main content
        main_content = ft.Column(
            [
                ft.Row(
                    [self.source_dropdown, self.target_dropdown, self.settings_button]
                ),
                ft.Divider(),
                ft.Text("Input Text", size=16, weight=ft.FontWeight.BOLD),
                self.input_text,
                self.process_button,
                ft.Divider(),
                ft.Text("Flashcard Information", size=16, weight=ft.FontWeight.BOLD),
                self.stack_name,
                self.stack_description,
                ft.Divider(),
                ft.Text("Generated Flashcards", size=16, weight=ft.FontWeight.BOLD),
                flashcard_table_container,
                ft.Row(
                    [
                        self.add_to_anki_button,
                        self.export_yaml_button,
                        self.export_csv_button,
                    ]
                ),
            ],
            scroll=ft.ScrollMode.AUTO,
            expand=True,
        )

        # Add the scrollable content to the page
        page.add(main_content)

    def on_source_language_change(self, e):
        self.source_language = e.control.value

    def on_target_language_change(self, e):
        self.target_language = e.control.value

    def open_settings(self, e):
        print("opening settings..")
        self.load_settings()

        # Create a settings dialog
        def close_dialog_save(e):
            self.llm_provider = provider_dropdown.value
            self.llm_model = model_input.value
            self.api_key = api_key_input.value
            self.base_url = base_url_input.value

            # Save settings to local storage
            settings_dir = os.path.join(os.path.expanduser("~"), ".flashcard_creator")
            os.makedirs(settings_dir, exist_ok=True)

            settings_path = os.path.join(settings_dir, "settings.json")
            settings = {}
            if os.path.exists(settings_path):
                try:
                    with open(settings_path, "r") as f:
                        settings = json.load(f)
                except:
                    pass

            # Update provider-specific settings
            settings[self.llm_provider] = {
                "model": self.llm_model,
                "base_url": self.base_url,
                "api_key": self.api_key,
            }

            with open(settings_path, "w") as f:
                json.dump(settings, f, indent=4)

            e.page.close(dialog)
            e.page.update()

        # Load settings if they exist
        settings_path = os.path.join(
            os.path.expanduser("~"), ".flashcard_creator", "settings.json"
        )
        settings = {}
        if os.path.exists(settings_path):
            with open(settings_path, "r") as f:
                try:
                    settings = json.load(f)
                    print(settings)
                    self.llm_provider = settings.get("provider", self.llm_provider)
                    # Load provider-specific settings
                    provider_settings = settings.get(self.llm_provider, {})
                    self.llm_model = provider_settings.get("model", self.llm_model)
                    self.base_url = provider_settings.get("base_url", self.base_url)
                    self.api_key = provider_settings.get("api_key", self.api_key)
                except:
                    pass

        provider_dropdown = ft.Dropdown(
            label="LLM Provider",
            options=[
                ft.dropdown.Option("openai"),
                ft.dropdown.Option("anthropic"),
                ft.dropdown.Option("vertexai"),
                ft.dropdown.Option("huggingface"),
                ft.dropdown.Option("deepseek"),
            ],
            value=self.llm_provider,
        )

        model_input = ft.TextField(label="Model Name", value=self.llm_model)
        base_url_input = ft.TextField(label="Base URL (optional)", value=self.base_url)

        api_key_input = ft.TextField(label="API Key", value=self.api_key, password=True)

        dialog = ft.AlertDialog(
            title=ft.Text("Settings"),
            content=ft.Column(
                [provider_dropdown, model_input, base_url_input, api_key_input],
                width=400,
                height=400,
            ),
            actions=[
                ft.TextButton("Cancel", on_click=lambda e: e.page.close(dialog)),
                ft.TextButton("Save", on_click=close_dialog_save),
            ],
        )

        e.page.open(dialog)
        # e.page.dialog = dialog
        # dialog.open = True
        e.page.update()

    def on_process_text(self, e):
        input_text = self.input_text.value
        if not input_text:
            e.page.open(
                ft.SnackBar(content=ft.Text("Please enter some text to process"))
            )
            return

        logger.info("Processing input text")

        # Use LiteLLM to process the text and generate flashcards
        flashcards = self.generate_flashcards(input_text, e.page)

        # Update the data table with the generated flashcards
        self.update_flashcard_table(flashcards, e.page)

    def generate_flashcards(self, text: str, page: ft.Page = None) -> Dict:
        """Use LiteLLM to generate flashcards from the input text."""
        from litellm import completion

        # Show progress bar
        progress = None
        if page:
            progress = ft.ProgressBar(width=600)
            page.add(
                ft.Column([ft.Text("Generating flashcards...", size=16), progress])
            )
            page.update()

        logger.info(
            f"Generating flashcards: {self.source_language} -> {self.target_language}"
        )
        logger.info(f"Using model: {self.llm_provider}/{self.llm_model}")

        prompt = f"""You are a language learning assistant creating flashcards to help students learn {self.target_language} from {self.source_language}.

    TASK: Create effective flashcards from the provided text.

    INSTRUCTIONS:
    1. Extract important vocabulary, phrases, and concepts from the text
    2. Create flashcards with {self.source_language} on the front and {self.target_language} on the back
    3. For vocabulary words:
    - Include articles for nouns (e.g., "der/die/das" in German)
    - Include plural forms where appropriate
    - Include gender information for languages that have grammatical gender
    4. For verbs, include the infinitive form and note if irregular
    5. For idiomatic expressions, provide the closest equivalent in the target language
    6. Choose content that represents different difficulty levels
    7. Ensure translations are accurate and natural-sounding in the target language
    8. Make sure to create flashcards for ALL relevant words in the input! If you are unsure, err on the side of too many flashcards.

    OUTPUT FORMAT:
    Return your response in valid YAML format with the following structure:
    ```yaml
    name: [Create a concise, descriptive name for this flashcard set]
    description: [Write a brief description of the content covered]
    cards:
    - "[source term 1]": "[target translation 1]"
    - "[source term 2]": "[target translation 2]"
    - "[source phrase]": "[target translation with grammatical notes if needed]"
    ```

    For example, for Chinese -> German, based on some vocab text including the following words, 
    your output could look like this:

    ```yaml
    name: Basic Chinese to German Vocabulary
    description: Essential vocabulary and phrases translated from Chinese to German
    cards:
    - "你好": "Hallo"
    - "谢谢": "Danke"
    - "再见": "Auf Wiedersehen"
    - "苹果": "der Apfel (pl. die Äpfel)"
    - "书": "das Buch (pl. die Bücher)"
    - "学生": "der/die Student/Studentin (pl. die Studenten/Studentinnen)"
    - "喝水": "Wasser trinken (verb, regular)"
    - "吃饭": "essen (verb, irregular: isst, aß, gegessen)"
    - "我很高兴认识你": "Es freut mich, dich kennenzulernen"
    - "慢走": "Komm gut nach Hause (idiom.)"
    ```
        
    Here's the text to process:

    {text}
    """

        try:
            # Set the API key for the selected provider
            # Load provider-specific settings
            settings_path = os.path.join(
                os.path.expanduser("~"), ".flashcard_creator", "settings.json"
            )
            settings = {}
            if os.path.exists(settings_path):
                with open(settings_path, "r") as f:
                    try:
                        settings = json.load(f)
                    except:
                        pass
            provider_settings = settings.get(self.llm_provider, {})
            api_key = provider_settings.get("api_key", "")
            os.environ[f"{self.llm_provider.upper()}_API_KEY"] = api_key

            logger.info("Sending request to LLM API...")

            # Use LiteLLM to generate flashcards
            response = completion(
                model=f"{self.llm_provider}/{self.llm_model}",
                api_base=self.base_url if self.base_url else None,
                messages=[{"role": "user", "content": prompt}],
            )

            logger.info("Received response from LLM API")

            # Extract the YAML content from the response
            yaml_content = response.choices[0].message.content

            logger.info("Extracting YAML content from response")

            # Extract YAML content from code blocks if present
            if "```yaml" in yaml_content:
                # Extract content between yaml and the next
                yaml_content = (
                    yaml_content.split("```yaml", 1)[1].split("```")[0].strip()
                )
            elif "```" in yaml_content:
                # Try to extract content between any
                yaml_blocks = yaml_content.split("```")
                if len(yaml_blocks) >= 3:  # At least one complete code block
                    yaml_content = yaml_blocks[1].strip()

            # Parse the YAML content
            logger.info("Parsing YAML content")
            flashcards = yaml.safe_load(yaml_content)
            logger.info(
                f"Generated {len(flashcards.get('cards', []))} flashcards successfully"
            )

            return flashcards
        except Exception as e:
            logger.error(f"Error generating flashcards: {e}", exc_info=True)
            return {"name": "", "description": "", "cards": []}
        finally:
            # Remove progress bar
            if page and progress:
                page.controls.pop()
                page.update()

    def update_flashcard_table(self, flashcards: Dict, page: ft.Page):
        """Update the flashcard table with the generated flashcards."""
        self.flashcards = flashcards

        # Set the stack name and description
        self.stack_name.value = flashcards.get("name", "")
        self.stack_description.value = flashcards.get("description", "")

        # Clear the existing rows
        self.flashcard_table.rows.clear()

        # Add a row for each flashcard
        for card in flashcards.get("cards", []):
            if isinstance(card, dict):
                for front, back in card.items():
                    self.add_row_to_table(front, back)
            else:
                # Handle case where card is a string with format "front: back"
                if ": " in card:
                    front, back = card.split(": ", 1)
                    self.add_row_to_table(front, back)

        # Add a row for adding new flashcards
        self.add_new_card_row()

        # Update the page
        page.update()

    def add_row_to_table(self, front, back):
        """Add a row to the flashcard table."""
        # Calculate width to make text fields take approximately 40% of screen width each
        text_field_width = 400  # Adjust this value based on typical screen width

        front_field = ft.TextField(
            value=front,
            width=text_field_width,
            multiline=True,  # Allow multiple lines for longer text
            min_lines=1,
            max_lines=3,
        )

        back_field = ft.TextField(
            value=back,
            width=text_field_width,
            multiline=True,  # Allow multiple lines for longer text
            min_lines=1,
            max_lines=3,
        )

        delete_button = ft.IconButton(
            icon=ft.icons.DELETE,
            on_click=lambda e, f=front_field, b=back_field: self.delete_flashcard(
                e, f, b
            ),
        )

        self.flashcard_table.rows.append(
            ft.DataRow(
                cells=[
                    ft.DataCell(front_field),
                    ft.DataCell(back_field),
                    ft.DataCell(delete_button),
                ]
            )
        )

    def add_new_card_row(self):
        """Add a row for adding new flashcards."""
        # Calculate width to make text fields take approximately 40% of screen width each
        text_field_width = 400  # Adjust this value based on typical screen width

        new_front_field = ft.TextField(
            hint_text="New Front",
            width=text_field_width,
            multiline=True,
            min_lines=1,
            max_lines=3,
        )

        new_back_field = ft.TextField(
            hint_text="New Back",
            width=text_field_width,
            multiline=True,
            min_lines=1,
            max_lines=3,
        )

        add_button = ft.IconButton(
            icon=ft.icons.ADD,
            on_click=lambda e: self.add_flashcard(e, new_front_field, new_back_field),
        )

        self.flashcard_table.rows.append(
            ft.DataRow(
                cells=[
                    ft.DataCell(new_front_field),
                    ft.DataCell(new_back_field),
                    ft.DataCell(add_button),
                ]
            )
        )

    def delete_flashcard(self, e, front_field, back_field):
        """Delete a flashcard from the table."""
        for i, row in enumerate(self.flashcard_table.rows):
            if (
                row.cells[0].content == front_field
                and row.cells[1].content == back_field
            ):
                self.flashcard_table.rows.pop(i)
                e.page.update()
                return

    def add_flashcard(self, e, front_field, back_field):
        """Add a new flashcard to the table."""
        if not front_field.value or not back_field.value:
            e.page.open(
                ft.SnackBar(content=ft.Text("Please enter both front and back values"))
            )
            e.page.update()
            return

        # Add a new row with the entered values
        self.add_row_to_table(front_field.value, back_field.value)

        # Clear the input fields
        front_field.value = ""
        back_field.value = ""

        # Update the page
        e.page.update()

    def add_to_anki(self, e):
        """Add the flashcards to Anki using AnkiConnect."""
        # Collect the flashcards from the table
        cards = []
        for row in self.flashcard_table.rows[:-1]:  # Exclude the last row (add row)
            front = row.cells[0].content.value
            back = row.cells[1].content.value
            if front and back:
                cards.append({"front": front, "back": back})

        if not cards:
            e.page.open(ft.SnackBar(content=ft.Text("No flashcards to add")))
            return

        # Create a dialog for selecting the Anki deck
        decks = self.get_anki_decks()
        if not decks:
            e.page.open(
                ft.SnackBar(
                    content=ft.Text("Failed to connect to Anki or no decks found")
                )
            )
            e.page.snack_bar.open = True
            e.page.update()
            return

        deck_dropdown = ft.Dropdown(
            label="Select Deck",
            options=[ft.dropdown.Option(deck) for deck in decks],
            width=300,
        )

        def add_cards(e):
            selected_deck = deck_dropdown.value
            if not selected_deck:
                e.page.open(ft.SnackBar(content=ft.Text("Please select a deck")))
                return

            success = self.create_anki_cards(selected_deck, cards)
            e.page.close(dialog)
            e.page.update()

            if success:
                e.page.open(
                    ft.SnackBar(
                        content=ft.Text(
                            f"Added {len(cards)} flashcards to {selected_deck}"
                        )
                    )
                )
            else:
                e.page.open(
                    ft.SnackBar(content=ft.Text("Failed to add flashcards to Anki"))
                )

        dialog = ft.AlertDialog(
            title=ft.Text("Add to Anki"),
            content=ft.Column([deck_dropdown], width=400, height=100),
            actions=[
                ft.TextButton(
                    "Cancel", on_click=lambda e: setattr(dialog, "open", False)
                ),
                ft.TextButton("Add", on_click=add_cards),
            ],
        )

        e.page.open(dialog)
        e.page.update()

    def get_anki_decks(self) -> List[str]:
        """Get the list of decks from Anki using AnkiConnect."""
        result = self.anki.get_decks()
        if isinstance(result, Ok):
            return result.value
        else:
            logger.error(f"Error getting Anki decks: {result.value}")
            return []

    def create_anki_cards(self, deck: str, cards: List[Dict[str, str]]) -> bool:
        """Create flashcards in Anki using Anki-connect."""
        result = self.anki.create_cards(deck, cards)
        if isinstance(result, Ok):
            return result.value
        else:
            logger.error(f"Error creating Anki cards: {result.value}")
            return False

    def export_as_yaml(self, e):
        """Export the flashcards as a YAML file."""
        # Collect the flashcards from the table
        cards = []
        for row in self.flashcard_table.rows[:-1]:  # Exclude the last row (add row)
            front = row.cells[0].content.value
            back = row.cells[1].content.value
            if front and back:
                cards.append({front: back})

        if not cards:
            e.page.open(ft.SnackBar(content=ft.Text("No flashcards to export")))
            return

        # Create the YAML content
        yaml_content = {
            "name": self.stack_name.value,
            "description": self.stack_description.value,
            "cards": cards,
        }

        # Create a file picker
        def save_file(e: ft.FilePickerResultEvent):
            if e.path:
                try:
                    with open(e.path, "w", encoding="utf-8") as f:
                        yaml.dump(yaml_content, f, allow_unicode=True)
                    e.page.open(ft.SnackBar(content=ft.Text(f"Saved to {e.path}")))
                except Exception as ex:
                    e.page.open(
                        ft.SnackBar(content=ft.Text(f"Error saving file: {ex}"))
                    )

        self.file_picker.on_result = save_file
        e.page.update()
        self.file_picker.save_file(
            file_name=f"{self.stack_name.value or 'flashcards'}.yaml",
            file_type=ft.FilePickerFileType.CUSTOM,
            allowed_extensions=["yaml"],
        )

    def export_as_csv(self, e):
        """Export the flashcards as a CSV file."""
        # Collect the flashcards from the table
        cards = []
        for row in self.flashcard_table.rows[:-1]:  # Exclude the last row (add row)
            front = row.cells[0].content.value
            back = row.cells[1].content.value
            if front and back:
                cards.append([front, back])

        if not cards:
            e.page.open(ft.SnackBar(content=ft.Text("No flashcards to export")))
            return

        # Create a file picker
        def save_file(e: ft.FilePickerResultEvent):
            if e.path:
                try:
                    with open(e.path, "w", encoding="utf-8", newline="") as f:
                        writer = csv.writer(f)
                        writer.writerow(["Front", "Back"])
                        writer.writerows(cards)
                    e.page.open(ft.SnackBar(content=ft.Text(f"Saved to {e.path}")))
                except Exception as ex:
                    e.page.open(
                        ft.SnackBar(content=ft.Text(f"Error saving file: {ex}"))
                    )

        self.file_picker.on_result = save_file
        e.page.update()
        self.file_picker.save_file(
            file_name=f"{self.stack_name.value or 'flashcards'}.csv",
            file_type=ft.FilePickerFileType.CUSTOM,
            allowed_extensions=["csv"],
        )


def main(page: ft.Page):
    app = FlashcardCreator()
    app.build_ui(page)


if __name__ == "__main__":
    ft.app(target=main)