#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Phase 3 (Alternate Method): Calibre Auto-Import
Imports decrypted kfx-zip files from the Extracted_Books folder into Calibre,
then clears the folder after a successful import run.
"""

import os
import sys
import subprocess
import threading
import time


def find_all_kfx_zip_files(extracted_books_path):
    """Find all *.kfx-zip files in the Extracted_Books output folder."""
    from modules.utils import print_error

    kfx_zip_files = []

    try:
        if not os.path.exists(extracted_books_path):
            return []

        for fname in os.listdir(extracted_books_path):
            if fname.lower().endswith('.kfx-zip'):
                kfx_zip_files.append(os.path.join(extracted_books_path, fname))

        return kfx_zip_files

    except Exception as e:
        print_error(f"Error scanning Extracted_Books directory: {e}")
        return []


def extract_asin_from_kfx_zip_filename(filename):
    """
    Extract ASIN from a kfx-zip filename.
    Expected pattern: {ASIN}_EBOK.kfx-zip  →  ASIN
    """
    base = os.path.splitext(os.path.basename(filename))[0]   # "B0BYW6MK4J_EBOK"
    if '_' in base:
        return base.split('_')[0]
    return base


def check_book_exists_in_calibre(library_path, asin):
    """
    Check if a book with the given ASIN already exists in Calibre library.
    Returns: (exists: bool, book_id: str or None, title: str or None)
    """
    import sqlite3

    if not asin or len(asin) < 5:
        return False, None, None

    try:
        db_path = os.path.join(library_path, "metadata.db")
        if not os.path.exists(db_path):
            return False, None, None

        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()

        cursor.execute("""
            SELECT books.id, books.title
            FROM books
            JOIN books_identifiers_link ON books.id = books_identifiers_link.book
            JOIN identifiers ON books_identifiers_link.id = identifiers.id
            WHERE identifiers.type = 'asin' AND identifiers.val = ?
        """, (asin,))

        result = cursor.fetchone()
        conn.close()

        if result:
            return True, str(result[0]), result[1]

        return False, None, None

    except Exception:
        return False, None, None


def get_book_title_from_calibre(library_path, book_id):
    """Get book title from Calibre library using book ID."""
    import sqlite3

    if not book_id:
        return None

    try:
        db_path = os.path.join(library_path, "metadata.db")
        if not os.path.exists(db_path):
            return None

        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT title FROM books WHERE id = ?", (book_id,))
        result = cursor.fetchone()
        conn.close()

        if result:
            return result[0]
        return None

    except Exception:
        return None


def import_single_kfx_zip(book_path, library_path, timeout_seconds=180, raw_log_path=None, idx=None, total=None, working_dir=None, force_metadata_fix=False):
    """
    Import a single kfx-zip file into Calibre.

    Args:
        working_dir:        Working directory used for temporary cover extraction files.
                            Required for cover extraction on the fallback metadata-fix path.
        force_metadata_fix: When True, skip the KFX metadata reader check and always
                            run the metadata fix helper (used for debug/testing via
                            debug_force_metadata_fix config option).

    Returns:
        (success, book_id, error_msg, timed_out, already_exists, book_title, kfx_reader_ran)
    """
    from modules.utils import display_progress_timer, append_to_raw_log
    from phases.phase_03_calibre_import import (
        parse_kfx_metadata_from_output,
        extract_cover_from_book,
        fix_book_metadata_in_calibre,
    )

    cmd = [
        'calibredb', 'add',
        '-1',
        book_path,
        f'--library-path={library_path}'
    ]

    try:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            encoding='utf-8',
            errors='replace'
        )

        timer_stopped = threading.Event()
        book_label = os.path.splitext(os.path.basename(book_path))[0]
        counter = f"[{idx:02d}/{total:02d}] " if (idx is not None and total is not None) else ""
        timer_message = f"Importing.. {counter}{book_label}"
        timer_thread = threading.Thread(
            target=display_progress_timer,
            args=(timer_message, timeout_seconds, timer_stopped),
            daemon=True
        )
        timer_thread.start()

        try:
            stdout, stderr = process.communicate(timeout=timeout_seconds)
            result_returncode = process.returncode
            timer_stopped.set()
            timer_thread.join(timeout=1)
            print("\r" + " " * 80 + "\r", end='', flush=True)
        except subprocess.TimeoutExpired:
            timer_stopped.set()
            timer_thread.join(timeout=1)
            print("\r" + " " * 80 + "\r", end='', flush=True)
            process.kill()
            stdout, stderr = process.communicate()
            result_returncode = -1

        # Log to RAW log if enabled
        if raw_log_path:
            book_name = os.path.basename(book_path)
            append_to_raw_log(
                raw_log_path,
                cmd,
                stdout,
                stderr,
                result_returncode,
                book_info=book_name
            )

        if result_returncode == 0 and stdout:
            for line in stdout.split('\n'):
                if 'Added book ids:' in line:
                    ids_str = line.split('Added book ids:')[1].strip()
                    book_id = ids_str.split(',')[0].strip() if ids_str else None

                    # Standard Calibre installs print this line when the KFX
                    # metadata reader already applied the correct title/author/cover.
                    # Non-standard installs skip the reader, leaving ASIN-as-title /
                    # Unknown-as-author and a blank cover.
                    # force_metadata_fix overrides this for debug/testing purposes.
                    kfx_reader_ran = (
                        'KFX metadata reader (from KFX Input) activated' in stdout
                        and not force_metadata_fix
                    )

                    if book_id and not kfx_reader_ran:
                        kfx_title, kfx_authors = parse_kfx_metadata_from_output(stdout)

                        # Extract cover from the kfx-zip source file.
                        # ebook-meta uses the KFX Input plugin to read kfx-zip files,
                        # so the cover is accessible the same way as for .azw files.
                        # Best-effort: failure is silent and non-blocking.
                        kfx_cover_path = None
                        if working_dir:
                            kfx_cover_path = extract_cover_from_book(
                                book_path, working_dir, raw_log_path=raw_log_path
                            )

                        if kfx_title or kfx_authors or kfx_cover_path:
                            fix_book_metadata_in_calibre(
                                library_path, book_id,
                                kfx_title, kfx_authors,
                                cover_path=kfx_cover_path,
                                raw_log_path=raw_log_path
                            )

                        # Clean up the temporary cover file after handoff
                        if kfx_cover_path and os.path.exists(kfx_cover_path):
                            try:
                                os.remove(kfx_cover_path)
                            except Exception:
                                pass

                    book_title = None
                    if book_id:
                        book_title = get_book_title_from_calibre(library_path, book_id)

                    return True, book_id, None, False, False, book_title, kfx_reader_ran

        error_msg = stderr if stderr else "Unknown error"
        already_exists = "already exist in the database" in error_msg

        return False, None, error_msg, False, already_exists, None, False

    except subprocess.TimeoutExpired:
        if raw_log_path:
            book_name = os.path.basename(book_path)
            from modules.utils import append_to_raw_log as _arl
            _arl(raw_log_path, cmd, '', '', -1, book_info=book_name)
        return False, None, f"Timeout after {timeout_seconds} seconds", True, False, None, False
    except Exception as e:
        return False, None, str(e), False, False, None, False


def clear_extracted_books_folder(extracted_books_path):
    """
    Remove all files from the Extracted_Books folder after a successful import run.
    The folder itself is preserved for the next run.
    """
    from modules.utils import print_step, print_ok, print_warn, print_error, rmtree_with_retry

    if not os.path.exists(extracted_books_path):
        return

    print_step("Clearing Extracted_Books folder...")

    removed = 0
    failed = 0

    try:
        for fname in os.listdir(extracted_books_path):
            fpath = os.path.join(extracted_books_path, fname)
            try:
                if os.path.isfile(fpath):
                    os.remove(fpath)
                    removed += 1
                elif os.path.isdir(fpath):
                    rmtree_with_retry(fpath)
                    removed += 1
            except Exception as e:
                print_warn(f"  Could not remove {fname}: {e}")
                failed += 1

        if failed == 0:
            print_ok(f"Extracted_Books folder cleared ({removed} item(s) removed)")
        else:
            print_warn(f"Cleared {removed} item(s); {failed} item(s) could not be removed")

    except Exception as e:
        print_error(f"Failed to clear Extracted_Books folder: {e}")


def run_phase_3_alt(extracted_books_path, calibre_path, working_dir, extraction_stats, config=None):
    """
    Run Phase 3 (Alternate Method): Calibre Auto-Import

    Imports *.kfx-zip files from extracted_books_path into Calibre,
    then clears the folder after the import run completes.

    Args:
        extracted_books_path: Folder containing decrypted kfx-zip files
        calibre_path: Calibre library path
        working_dir: Working directory for logs / failed-books tracking
        extraction_stats: Statistics from Phase 1 Alt (used to exclude failed ASINs)
        config: Configuration dict (optional)

    Returns:
        dict with keys:
            - success: bool
            - imported_count: int
            - book_ids: list of str
            - results: dict with detailed counts
    """
    from modules.utils import (
        print_step, print_ok, print_warn, print_error, is_calibre_running,
        get_raw_log_path, cleanup_old_raw_logs, write_raw_log_header, append_to_failed_books
    )

    # Safety-net: Calibre running check is handled in key_finder.py before Phase 3
    # is ever called. If Calibre is somehow still running here, bail out immediately
    # rather than waiting - this prevents any risk of database corruption.
    if is_calibre_running():
        print_warn("Calibre is still running - Phase 3 import aborted to protect database.")
        print_warn("Close Calibre and re-run the script to import your books.")
        print()
        return {
            'success': False,
            'imported_count': 0,
            'book_ids': [],
            'results': {
                'total': 0,
                'success': 0,
                'failed': 0,
                'skipped': 0,
                'book_ids': [],
                'failed_books': [],
                'skipped_books': []
            }
        }

    # Scan for kfx-zip files
    print_step("[PHASE 3a] Scanning Extracted_Books for kfx-zip files...")
    kfx_zip_files = find_all_kfx_zip_files(extracted_books_path)

    if not kfx_zip_files:
        print_warn("No kfx-zip files found in Extracted_Books folder")
        print()
        return {
            'success': False,
            'imported_count': 0,
            'book_ids': [],
            'results': {
                'total': 0,
                'success': 0,
                'failed': 0,
                'skipped': 0,
                'book_ids': [],
                'failed_books': [],
                'skipped_books': []
            }
        }

    print_ok(f"Found {len(kfx_zip_files)} kfx-zip file(s) to import")
    print()

    # Build exclusion list from failed Phase 1 ASINs
    exclude_asins = []
    if extraction_stats.get('failed_books'):
        exclude_asins.extend([asin for asin, _, _ in extraction_stats['failed_books']])

    # Pre-check for duplicates already in Calibre
    print_step("[PHASE 3b] Checking for existing books in Calibre library...")
    skip_book_paths = []
    for book_path in kfx_zip_files:
        asin = extract_asin_from_kfx_zip_filename(book_path)
        if asin in exclude_asins:
            continue
        exists, book_id, title = check_book_exists_in_calibre(calibre_path, asin)
        if exists:
            skip_book_paths.append((book_path, book_id, title))

    if skip_book_paths:
        print_warn(f"Found {len(skip_book_paths)} book(s) already in Calibre - will skip:")
        for bp, bid, btitle in skip_book_paths:
            display_title = btitle if btitle else extract_asin_from_kfx_zip_filename(bp)
            print(f"  - {display_title} (ID: {bid})")
        print()

    skip_paths_set = {bp for bp, _, _ in skip_book_paths}
    books_to_import = [f for f in kfx_zip_files if f not in skip_paths_set]

    if not books_to_import:
        print_warn("No new kfx-zip files to import - all books are already in Calibre")
        print()
        return {
            'success': True,
            'imported_count': 0,
            'book_ids': [],
            'results': {
                'total': len(kfx_zip_files),
                'success': 0,
                'failed': 0,
                'skipped': len(skip_book_paths),
                'book_ids': [],
                'failed_books': [],
                'skipped_books': [(extract_asin_from_kfx_zip_filename(bp), btitle)
                                  for bp, _, btitle in skip_book_paths]
            }
        }

    # Initialise RAW log if enabled
    enable_raw_logs = config.get('enable_raw_logs', False) if config else False
    raw_log_path = None
    if enable_raw_logs:
        raw_log_path = get_raw_log_path(working_dir, 'import')
        write_raw_log_header(raw_log_path)   # reads Kindle version + mode from config
        import_logs_dir = os.path.join(working_dir, "Logs", "02_Import")
        cleanup_old_raw_logs(import_logs_dir, keep_count=10)

    # Import books one at a time
    print_step("Importing kfx-zip files to Calibre...")
    print("--------------------------------------------------")
    print()

    results = {
        'total': len(kfx_zip_files),
        'success': 0,
        'failed': 0,
        'skipped': len(skip_book_paths),
        'book_ids': [],
        'failed_books': [],
        'skipped_books': [(extract_asin_from_kfx_zip_filename(bp), btitle)
                          for bp, _, btitle in skip_book_paths],
        'kfx_integration_active': False,   # True if KFX Input applied metadata natively
        'metadata_fixes_applied': 0,       # Count of books fixed by our helper
    }

    total = len(books_to_import)

    # debug_force_metadata_fix: simulate the fallback path on any machine
    # (set in config as debug_force_metadata_fix: true for testing)
    _force_fix = bool(config.get('debug_force_metadata_fix', False)) if config else False

    for idx, book_path in enumerate(books_to_import, 1):
        book_name = os.path.basename(book_path)
        asin = extract_asin_from_kfx_zip_filename(book_path)

        success, book_id, error_msg, timed_out, already_exists, book_title, kfx_reader_ran = import_single_kfx_zip(
            book_path,
            calibre_path,
            timeout_seconds=180,
            raw_log_path=raw_log_path,
            idx=idx,
            total=total,
            working_dir=working_dir,
            force_metadata_fix=_force_fix
        )

        counter = f"[{idx:02d}/{total:02d}]"

        # Track KFX Input plugin integration status across all books
        if success:
            if kfx_reader_ran:
                results['kfx_integration_active'] = True
            else:
                results['metadata_fixes_applied'] += 1

        if success and book_id:
            if book_title:
                print_ok(f"  {counter} Imported: {book_title} (ID: {book_id})")
            else:
                print_ok(f"  {counter} Imported (ID: {book_id}) [{asin}]")

            results['success'] += 1
            results['book_ids'].append(book_id)

        elif timed_out:
            print_error(f"  {counter} TIMEOUT [{asin}]")
            results['failed'] += 1
            results['failed_books'].append((book_name, "Timeout"))
            if working_dir:
                append_to_failed_books(working_dir, asin, asin)

        elif already_exists:
            display = book_title if book_title else asin
            print(f"  {counter} [SKIPPED] {display} - already in Calibre library")
            results['skipped'] += 1
            results['skipped_books'].append((asin, display))

        else:
            print_error(f"  {counter} FAILED [{asin}]")
            results['failed'] += 1
            results['failed_books'].append((book_name, error_msg))
            if working_dir:
                append_to_failed_books(working_dir, asin, asin)

    print()

    # Display import results summary
    print("--------------------------------------------------")
    print_step("Import Results:")
    print()

    if results['success'] > 0:
        print_ok(f"Successfully imported: {results['success']} kfx-zip file(s)")
        print(f"      Book IDs: {', '.join(results['book_ids'])}")
    else:
        print_warn("No books were imported")

    if results['skipped'] > 0:
        print_warn(f"Skipped: {results['skipped']} book(s) already in Calibre library")

    if results['failed'] > 0:
        print_error(f"Failed: {results['failed']} book(s)")

    print()

    # Append KFX Input plugin integration status to startup.log
    from modules.utils import append_to_startup_log
    integration_active = results.get('kfx_integration_active', False)
    fixes_applied = results.get('metadata_fixes_applied', 0)

    if integration_active:
        _status_lines = [
            "KFX Metadata Reader  : ACTIVE",
            "Status               : KFX Input plugin applied metadata natively (ALT method)",
            "Metadata Fix Helper  : NOT NEEDED (0 books fixed)",
        ]
    else:
        _cause = "debug_force_metadata_fix=true" if _force_fix else "single-user install, non-standard path, or Calibre version"
        _status_lines = [
            "KFX Metadata Reader  : NOT DETECTED",
            f"Status               : KFX Input plugin did not apply metadata ({_cause}) [ALT method]",
            f"Metadata Fix Helper  : APPLIED to {fixes_applied} book(s) via calibredb set_metadata",
        ]
    append_to_startup_log(working_dir, "KFX INPUT PLUGIN INTEGRATION (ALT)", _status_lines)

    # Clear the Extracted_Books folder after import
    # Only clear if at least one book was successfully imported (or all were skipped = already processed)
    any_processed = results['success'] > 0 or results['skipped'] > 0
    if any_processed:
        clear_extracted_books_folder(extracted_books_path)
    else:
        print_warn("Extracted_Books folder NOT cleared - no books were successfully processed")
    print()

    imported_count = results['success']
    book_ids = results['book_ids']

    return {
        'success': imported_count > 0 or len(skip_book_paths) > 0,
        'imported_count': imported_count,
        'book_ids': book_ids,
        'results': results
    }
