import re, zipfile, json, io, markdown2, re, base64
import urllib.parse
from nicegui import ui, app, run
from biblemateweb import get_translation, loading
from biblemateweb.fx.bible import BibleSelector
from agentmake.plugins.uba.lib.BibleParser import BibleVerseParser
from biblemateweb.fx.cloud_index_manager import get_drive_service, CloudIndexManager
from googleapiclient.http import MediaIoBaseUpload


class CloudNotepad:
    def __init__(self, content=""):
        self.text_content = content
        self.is_editing = True
        self.parser = BibleVerseParser(False, language=app.storage.user['ui_language'])
        
    def setup_ui(self):

        # --- Content Area ---
        # Card must be 'flex flex-col' so the child (textarea) can grow
        with ui.card().classes('w-full h-[75vh] p-0 flex flex-col'):
            
            # 1. Edit Mode: Text Area
            # We apply our custom 'full-height-textarea' class here
            self.textarea = ui.textarea(
                placeholder=get_translation("Start typing your notes here..."),
                value=self.text_content
            ).classes('w-full flex-grow full-height-textarea p-2 border-none focus:outline-none') \
             .props('flat squares resize-none') \
             .bind_visibility_from(self, 'is_editing')

            # 2. Read Mode: HTML Preview
            with ui.scroll_area().classes('w-full flex-grow p-2') \
                    .bind_visibility_from(self, 'is_editing', backward=lambda x: not x):
                self.html_view = ui.html(f'<div class="content-text">{self.text_content}</div>', sanitize=False).classes('w-full prose max-w-none')

    def toggle_mode(self):
        self.is_editing = not self.is_editing
        if not self.is_editing:
            self.update_preview()

    def update_preview(self):
        content = self.textarea.value or ''
        try:
            # Added your requested extras
            html_content = markdown2.markdown(
                content, 
                extras=["tables", "fenced-code-blocks", "toc", "cuddled-lists"]
            )
            html_content = self.parser.parseText(html_content)
            html_content = re.sub(r'''(onclick|ondblclick)="(bdbid|lex|cr|bcv|website)\((.*?)\)"''', r'''\1="emitEvent('\2', [\3]); return false;"''', html_content)
            html_content = re.sub(r"""(onclick|ondblclick)='(bdbid|lex|cr|bcv|website)\((.*?)\)'""", r"""\1='emitEvent("\2", [\3]); return false;'""", html_content)
            self.html_view.content = html_content
        except Exception as e:
            self.html_view.content = f"<p class='text-red-500'>Error: {str(e)}</p>"

    def download_file(self):
        content = self.textarea.value or ''
        if not content:
            ui.notify('Nothing to download!', type='warning')
            return
        ui.download(content.encode('utf-8'), 'biblemate_notes.md')
        ui.notify('Downloaded!', type='positive')
    
    async def handle_upload(self, e):
        try:
            content = await e.file.read()
            self.textarea.value = content.decode('utf-8')
            if not self.is_editing: self.update_preview()
            ui.notify('Loaded!', type='positive')
            self.upload.reset()
        except Exception as ex:
            ui.notify(f'Error: {str(ex)}', type='negative')
    
    def clear_text(self):
        self.textarea.value = ''
        self.html_view.content = ''
        ui.notify('Cleared!', type='info')

def notes(gui=None, bt=None, b=1, c=1, v=1, area=2, **_):
    if not bt:
        bt = gui.get_area_1_bible_text()
    elif bt in ("ORB", "OIB", "OPB", "ODB", "OLB"):
        bt = "OHGB"
    
    # Book Note
    if c == 0:
        v = 0

    # 1. Auth Check
    token = app.storage.user.get('google_token', "")
    if not token:
        with ui.card().classes('absolute-center'):
            ui.html('Sign in with Google to securely save and sync your personal notes across all your devices.<br><i><b>Data Policy Note:</b> BibleMate AI does not collect or store your personal notes. Your notes are saved directly within your own Google Account.</i>', sanitize=False)
            with ui.row().classes('w-full justify-center'):
                ui.button('Login with Google', on_click=lambda: ui.navigate.to('/login'))
            with ui.expansion(get_translation("BibleMate AI Data Policy & Privacy Commitment"), icon='privacy_tip').props('header-class="text-secondary"'):
                ui.html(
                    """<b>We respect your privacy.</b>
To keep your data private, BibleMate AI doesn't collect, store or share your personal information on its servers. 
Instead, daily logs or bible notes are saved exclusively on <b>your own Google Drive/Account.</b>, ensuring that you retain full control and ownership of your private data at all times.""", 
                    sanitize=False,
                )
        return

    bible_selector = None
    service = get_drive_service(token)
    index_mgr = CloudIndexManager(service)
    notepad = CloudNotepad()

    def change_note(version=None, book=1, chapter=1, verse=1):
        nonlocal gui
        _, app.storage.user['tool_book_number'], app.storage.user['tool_chapter_number'], app.storage.user['tool_verse_number'] = version, book, chapter, verse
        gui.load_area_2_content(title='Notes', sync=False)
    bible_selector = BibleSelector(version_options=[bt], on_book_changed=change_note, on_chapter_changed=change_note, on_verse_changed=change_note, chapter_zero=True, verse_zero=True)

    def refresh_ui():
        try:
            nonlocal gui, b, c, v
            active_area1_tab = gui.get_active_area1_tab()
            if active_area1_tab in app.storage.user and app.storage.user[active_area1_tab]["b"] == b and app.storage.user[active_area1_tab]["c"] == c:
                gui.change_area_1_bible_chapter(book=b, chapter=c, verse=v)
        except:
            import traceback
            traceback.print_exc()

    def get_filename(verse_id):
        return f"{verse_id}.json"

    def get_vid(): 
        return f"{b}_{c}_{v}"

    def load_current_note():
        vid = get_vid()
        try:
            filename = get_filename(vid)
            results = service.files().list(
                q=f"name='{filename}' and 'appDataFolder' in parents and trashed=false",
                spaces='appDataFolder',
                fields='files(id)'
            ).execute()
            files = results.get('files', [])
            
            if files:
                request = service.files().get_media(fileId=files[0]['id'])
                data = json.loads(request.execute())
                return data.get("content", "")
            return ""
        except Exception as e:
            ui.notify(f"Error loading: {e}", type='negative')
            return ""

    def save_note_sync(service, verse_id, content, filename):
        """
        Performs the slow network calls to Google Drive.
        Running this on the main thread would freeze the app.
        """
        import json, io
        from googleapiclient.http import MediaIoBaseUpload
        
        # Prepare the file data
        file_data = {"verse_id": verse_id, "content": content}
        media = MediaIoBaseUpload(
            io.BytesIO(json.dumps(file_data).encode('utf-8')), 
            mimetype='application/json'
        )
        
        # 1. Search for existing file
        results = service.files().list(
            q=f"name='{filename}' and 'appDataFolder' in parents and trashed=false",
            spaces='appDataFolder',
            fields='files(id)'
        ).execute()
        files = results.get('files', [])

        # 2. Update or Create
        if files:
            service.files().update(fileId=files[0]['id'], media_body=media).execute()
            return "updated"
        else:
            meta = {'name': filename, 'parents': ['appDataFolder']}
            service.files().create(body=meta, media_body=media).execute()
            return "created"

    async def save_current_note():
        # 1. Get Data
        vid = get_vid()
        content = notepad.textarea.value
        filename = get_filename(vid)
        
        # 2. Show Loading Spinner
        # (This prevents the user from clicking save twice)
        n = ui.notification('Saving to Cloud...', timeout=None, spinner=True)
        
        try:
            # 3. Run the heavy network call in a separate thread
            # 'service' is passed explicitly to be thread-safe
            result = await run.io_bound(
                save_note_sync, 
                service, 
                vid, 
                content, 
                filename
            )
            
            # 4. Update Index & UI (Back on the main thread)
            index_mgr.add_verse(vid)
            app.storage.user['cached_index'] = index_mgr.data
            
            n.dismiss()
            ui.notify('Saved successfully!', type='positive')
            refresh_ui()
            
        except Exception as e:
            n.dismiss()
            ui.notify(f"Error saving: {e}", type='negative')

    def delete_current_note():
        vid = get_vid()
        try:
            filename = get_filename(vid)
            results = service.files().list(
                q=f"name='{filename}' and 'appDataFolder' in parents and trashed=false",
                spaces='appDataFolder',
                fields='files(id)'
            ).execute()
            files = results.get('files', [])

            if files:
                service.files().delete(fileId=files[0]['id']).execute()
                index_mgr.remove_verse(vid)
                app.storage.user['cached_index'] = index_mgr.data
                notepad.textarea.value = ""
                ui.notify('Note deleted.')
                refresh_ui()
            else:
                ui.notify('Nothing to delete.')
        except Exception as e:
            ui.notify(f"Delete error: {e}", type='negative')

    async def run_rebuild():
        # Run the rebuild
        n = ui.notification('Rebuilding search index...', timeout=None, spinner=True)
        count = await run.io_bound(index_mgr.rebuild_index)
        n.dismiss()
        if count >= 0:
            app.storage.user['cached_index'] = index_mgr.data # Update session
            refresh_ui() # Refresh stats
            ui.notify(f'Search index rebuilt! Found {count} notes.', type='positive')
        else:
            ui.notify('Rebuild failed. Check console logs.', type='negative')

    def download_index_backup():
        """Generates a downloadable JSON file of the current index."""
        if not index_mgr.data:
            ui.notify('Index is empty!', type='warning')
            return
        try:
            # 1. Convert index data to pretty JSON string
            json_str = json.dumps(index_mgr.data, indent=2)
            # 2. Encode it for the browser (Data URI format)
            encoded_json = urllib.parse.quote(json_str)
            # 3. Trigger download
            ui.download(f'data:application/json,{encoded_json}', 'bible_index_backup.json')
            ui.notify('Download started.')
        except Exception as e:
            ui.notify(f'Download error: {e}', type='negative')

    # ---------------------------------------------------------
    # Import a single json or a zip
    # ---------------------------------------------------------

    def process_import_sync(file_bytes, is_zip, drive_service):
        """
        Parses the uploaded bytes (Zip or JSON) and uploads them to Drive.
        Returns a list of successfully imported verse_ids.
        """
        imported_ids = []
        files_to_process = {} # {filename: json_content_dict}

        try:
            # A. Parse the Input (Zip or Single JSON)
            if is_zip:
                with zipfile.ZipFile(io.BytesIO(file_bytes)) as z:
                    for filename in z.namelist():
                        if filename.endswith('.json') and filename != 'bible_index.json':
                            try:
                                content = z.read(filename)
                                data = json.loads(content)
                                files_to_process[filename] = data
                            except:
                                print(f"Skipping invalid file: {filename}")
            else:
                # Single JSON file
                try:
                    data = json.loads(file_bytes)
                    # Quick check if it's a note file
                    if 'verse_id' in data:
                        filename = f"{data['verse_id']}.json"
                        files_to_process[filename] = data
                except:
                    pass

            # B. Upload Loop
            # Note: This loops through every note. For 100+ notes, this takes time.
            for filename, data in files_to_process.items():
                verse_id = data.get('verse_id')
                if not verse_id: continue

                # Prepare content
                # We strictly re-dump the data to ensure clean JSON
                media = MediaIoBaseUpload(
                    io.BytesIO(json.dumps(data).encode('utf-8')), 
                    mimetype='application/json'
                )
                
                # 1. Check if file exists in Drive
                results = drive_service.files().list(
                    q=f"name='{filename}' and 'appDataFolder' in parents and trashed=false",
                    spaces='appDataFolder',
                    fields='files(id)'
                ).execute()
                existing_files = results.get('files', [])

                if existing_files:
                    # Update existing
                    file_id = existing_files[0]['id']
                    drive_service.files().update(fileId=file_id, media_body=media).execute()
                else:
                    # Create new
                    meta = {'name': filename, 'parents': ['appDataFolder']}
                    drive_service.files().create(body=meta, media_body=media).execute()
                
                imported_ids.append(verse_id)

        except Exception as e:
            print(f"Import process failed: {e}")
        
        return imported_ids

    async def handle_import(e):
        """
        UI Callback for the upload button.
        """
        #print(dir(e))
        # 1. Check login
        if not service:
            ui.notify('Please log in to Google first!', type='warning')
            return

        n = ui.notification('Processing Import... Please wait.', timeout=None, spinner=True)
        
        try:
            # 2. Read file bytes
            file_bytes = await e.file.read() 
            filename = getattr(e.file, 'name', 'unknown.json').lower()
            #print(filename)
            is_zip = filename.endswith('.zip')

            # Run background worker
            imported_ids = await run.io_bound(
                process_import_sync, 
                file_bytes, 
                is_zip, 
                service
            )

            # 4. Update Local Index
            if imported_ids:
                count = len(imported_ids)
                for vid in imported_ids:
                    index_mgr.add_verse(vid) # Updates local memory
                
                # Sync index to Drive once at the end
                index_mgr.save_to_drive() 
                app.storage.user['cached_index'] = index_mgr.data
                
                ui.notify(f'Successfully imported {count} notes!', type='positive')
                refresh_ui() # Update your stats/counters
                change_note(book=b, chapter=c, verse=v)
            else:
                ui.notify('No valid notes found to import.', type='warning')

        except Exception as err:
            ui.notify(f'Import failed: {err}', type='negative')
        finally:
            n.dismiss()

    # ---------------------------------------------------------
    # Download a single (the current) note
    # ---------------------------------------------------------
    
    def download_current_note():
        """
        Exports the currently visible note as a JSON file.
        This format is compatible with the 'Import' feature.
        """
        vid = get_vid() # Uses your existing helper
        content = notepad.textarea.value or ''
        
        if not content.strip():
            ui.notify('Cannot download an empty note.', type='warning')
            return

        try:
            # 1. Structure the data exactly like it is stored in Drive
            note_data = {
                "verse_id": vid,
                "content": content,
                "exported_at": "true" # Optional flag
            }
            
            # 2. Convert to JSON bytes
            json_str = json.dumps(note_data, indent=2)
            json_bytes = json_str.encode('utf-8')
            
            # 3. Encode to Base64 (Safe for all browsers/characters)
            b64_str = base64.b64encode(json_bytes).decode()
            
            # 4. Trigger Download
            filename = f"{vid}.json"
            ui.download(f'data:application/json;base64,{b64_str}', filename)
            ui.notify(f'Downloaded {filename}')
            
        except Exception as e:
            ui.notify(f'Download error: {e}', type='negative')

    # ---------------------------------------------------------
    # 1. The Heavy Lifter (Download ALL)
    # ---------------------------------------------------------
    def generate_zip_sync(data, drive_service):
        """
        Synchronous function to download files and create zip.
        Note: We pass 'data' and 'drive_service' explicitly.
        """
        zip_buffer = io.BytesIO()
        
        with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            # Add Index File
            zip_file.writestr('bible_index.json', json.dumps(data, indent=2))
            
            # Loop through notes
            for verse_id in data:
                try:
                    filename = f"{verse_id}.json" # Recreating get_filename logic here for safety
                    
                    # Search logic
                    results = drive_service.files().list(
                        q=f"name='{filename}' and 'appDataFolder' in parents and trashed=false",
                        spaces='appDataFolder',
                        fields='files(id)'
                    ).execute()
                    files = results.get('files', [])

                    if files:
                        file_id = files[0]['id']
                        request = drive_service.files().get_media(fileId=file_id)
                        file_content = request.execute()
                        zip_file.writestr(filename, file_content)
                except Exception as e:
                    print(f"Error zipping {verse_id}: {e}")

        # Finalize
        zip_buffer.seek(0)
        return base64.b64encode(zip_buffer.getvalue()).decode()

    # ---------------------------------------------------------
    # 2. The UI Function (Runs on main thread)
    # ---------------------------------------------------------
    async def download_all_notes_zip():
        if not index_mgr.data:
            ui.notify('No notes to download!', type='warning')
            return

        # 1. Show Spinner (Now it will actually render!)
        n = ui.notification('Downloading notes from Google Drive... Please wait.', timeout=None, spinner=True)
        
        try:
            # 2. Offload the heavy work to a background thread
            # This allows the UI to keep running while Python waits for the result
            b64_zip = await run.io_bound(generate_zip_sync, index_mgr.data, service)
            
            # 3. Trigger Download
            ui.download(f'data:application/zip;base64,{b64_zip}', 'biblemate_notes_backup.zip')
            
            n.dismiss()
            ui.notify('Download started!', type='positive')

        except Exception as e:
            n.dismiss()
            ui.notify(f'Zip error: {e}', type='negative')
    
    # Dialog to confirm deleting a note
    with ui.dialog() as delete_dialog, ui.card():
        ui.label('Are you sure you want to delete this note?')
        with ui.row().classes('justify-end w-full'):
            ui.button(get_translation("Cancel"), on_click=delete_dialog.close).props('flat text-color=secondary')
            ui.button('Delete', color='red', on_click=lambda: (delete_current_note(), delete_dialog.close()))

    # Bible Selection menu
    def additional_items():
        nonlocal gui, bible_selector, area
        with ui.button(icon='more_vert').props(f'flat round color={"white" if app.storage.user["dark_mode"] else "black"}'):
            with ui.menu():
                ui.menu_item(f'👁️ {get_translation("Read")} / {get_translation("Edit")}', on_click=notepad.toggle_mode)
                ui.separator()
                ui.menu_item(f'💾 {get_translation("Save")}', on_click=save_current_note)
                ui.menu_item(f'❌ {get_translation("Delete")}', on_click=delete_dialog.open)
                ui.menu_item(f'📥 {get_translation("Download")}', on_click=download_current_note)
                ui.menu_item(f'📥 {get_translation("Download All")}', on_click=download_all_notes_zip)
                ui.separator()
                with ui.menu_item(f'📤 {get_translation("Import")}', on_click=lambda: upload_element.run_method('pickFiles')):
                    ui.tooltip('Import a single note (*.json file) or a zip file containing multiple notes.')
                ui.separator()
                with ui.menu_item(f'🛠️ {get_translation("Rebuild Index")}', on_click=run_rebuild):
                    ui.tooltip('Rebuild search index for maintenance or repair.')
                ui.menu_item(f'📥 {get_translation("Download Index")}', on_click=download_index_backup)
                ui.separator()
                with ui.menu_item(f'🔒 {get_translation("Logout")}', on_click=lambda: (app.storage.user.clear(), ui.navigate.to('/'))):
                    ui.tooltip('Log out to keep your account secure, especially on shared devices.')
    bible_selector.create_ui(bt, b, c, v, additional_items=additional_items, show_versions=False)

    upload_element = ui.upload(
        on_upload=handle_import, 
        auto_upload=True
    ).props('accept=.json,.zip').classes('hidden')

    notepad.setup_ui()
    def load_initial_content():
        notepad.textarea.value = load_current_note()
    ui.timer(0, lambda: loading(load_initial_content), once=True)

