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

"""
Phase 1 (Alternate Method): Decryption & Extraction
Uses KFXArchiver (any version) to directly decrypt books into kfx-zip format.
No DRM key extraction - books are decrypted in-place to Extracted_Books folder.
"""

import os
import sys
import shutil
import subprocess
import threading
import time
from datetime import datetime

# Add modules to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from modules.utils import (
    print_step, print_ok, print_error, print_warn,
    check_write_permissions, rmtree_with_retry,
    get_raw_log_path, append_to_raw_log, cleanup_old_raw_logs, write_raw_log_header,
    SCRIPT_VERSION,
    append_to_failed_books, remove_from_failed_books,
    timed_choice_input
)

# Import shared helpers from the standard Phase 1
from phases.phase_01_key_extraction import (
    find_kindle_exe, create_temp_kindle_copy, cleanup_temp_kindle_copy,
    load_book_history, append_to_history, scan_kindle_content_directory,
)

# The alternate extractor executable filename is resolved dynamically from the
# extractor_path argument at runtime (via os.path.basename), so any future
# version (e.g. KFXArchiver300.exe) placed in code/tools/ is picked up
# automatically without any code changes here.


def find_kfx_zip_for_book(extracted_books_path, asin):
    """
    Check if a kfx-zip output file was generated for a given ASIN.
    Expected filename pattern: {ASIN}_EBOK.kfx-zip
    
    Returns the file path if found, or None.
    """
    if not os.path.exists(extracted_books_path):
        return None

    expected_filename = f"{asin}_EBOK.kfx-zip"
    expected_path = os.path.join(extracted_books_path, expected_filename)
    if os.path.exists(expected_path):
        return expected_path

    # Fallback: scan directory for any file starting with the ASIN
    try:
        for fname in os.listdir(extracted_books_path):
            if fname.startswith(asin) and fname.endswith('.kfx-zip'):
                return os.path.join(extracted_books_path, fname)
    except Exception:
        pass

    return None


def extract_single_book_alt(extractor_path, kindle_dir, book_folder, extracted_books_path,
                             output_k4i, asin, book_title, working_dir=None,
                             raw_log_path=None, book_prefix=""):
    """
    Run KFXArchiver (any version) for a single book folder (via temp copy).

    The extractor is called with:
        KFXArchiverXXX.exe <temp_dir> <extracted_books_path> <output_k4i>

    Success is determined by whether {ASIN}_EBOK.kfx-zip appears in extracted_books_path.

    Returns:
        (success: bool, error_msg: str or None, asin: str)
    """
    script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    user_home = os.path.expanduser("~")

    if working_dir is None:
        can_write, _ = check_write_permissions(script_dir)
        if can_write:
            working_dir = script_dir
        else:
            working_dir = os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")

    temp_dir = os.path.join(working_dir, "temp_extraction")
    timer_stopped = None
    timeout_seconds = 120  # Slightly longer – decryption is heavier than key extraction

    try:
        os.makedirs(temp_dir, exist_ok=True)

        # Copy extractor into Kindle dir if not already there
        extractor_in_kindle = os.path.join(kindle_dir, os.path.basename(extractor_path))
        if not os.path.exists(extractor_in_kindle):
            shutil.copy2(extractor_path, kindle_dir)

        # Create unique book subfolder in temp_extraction
        book_name = os.path.basename(book_folder)
        temp_book = os.path.join(temp_dir, book_name)

        # Clean any pre-existing temp book folder
        if os.path.exists(temp_book):
            success = rmtree_with_retry(temp_book, max_retries=5, retry_delay=0.5)
            if not success:
                return False, "Failed to clean existing temp book folder", asin

        try:
            shutil.copytree(book_folder, temp_book)
        except (OSError, PermissionError) as e:
            return False, f"Access denied copying book folder - {str(e)}", asin

        # Timer display thread (same style as standard Phase 1)
        timer_stopped = threading.Event()

        def display_extraction_timer():
            start_time = time.time()
            while not timer_stopped.is_set():
                elapsed = int(time.time() - start_time)
                minutes, seconds = divmod(elapsed, 60)
                timeout_mins, timeout_secs = divmod(timeout_seconds, 60)
                timer_str = f" [ {minutes:02d}:{seconds:02d} / {timeout_mins:02d}:{timeout_secs:02d} ]"
                print(f"\r{book_prefix}{timer_str}", end='', flush=True)
                time.sleep(1)

        timer_thread = threading.Thread(target=display_extraction_timer, daemon=True)
        timer_thread.start()

        # CRITICAL: 2-second delay before running extractor (same requirement as v28)
        time.sleep(2)

        # Run alternate extractor:
        #   arg1 = temp_dir  (books source)
        #   arg2 = extracted_books_path  (output destination for kfx-zip)
        #   arg3 = output_k4i  (k4i path – required by tool even if not used by us)
        cmd = [extractor_in_kindle, temp_dir, extracted_books_path, output_k4i]

        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout_seconds,
            cwd=kindle_dir,
            env=os.environ.copy()
        )

        timer_stopped.set()
        timer_thread.join(timeout=1)
        # Clear the timer line
        print("\r" + " " * 150 + "\r", end='', flush=True)
        print(f"{book_prefix}", end='', flush=True)

        # Log to RAW log if enabled
        if raw_log_path:
            append_to_raw_log(
                raw_log_path,
                cmd,
                result.stdout,
                result.stderr,
                result.returncode,
                book_info=f"{asin} - {book_title}"
            )

        # Success = kfx-zip file appeared in extracted_books_path
        kfx_zip_file = find_kfx_zip_for_book(extracted_books_path, asin)

        if not kfx_zip_file:
            if result.returncode != 0:
                error_msg = f"Decryption failed (exit code {result.returncode})"
            else:
                # Check if failure was due to KF8/MOBI format mismatch
                is_format_mismatch = (
                    'Invalid book format, maybe KF8/MOBI?' in result.stdout
                    or 'BookOpen error 14' in result.stdout
                )
                if is_format_mismatch:
                    error_msg = "KF8/MOBI book format - not supported by KFXArchiver"
                else:
                    error_msg = "No kfx-zip output file was generated"
            return False, error_msg, asin

        return True, None, asin

    except subprocess.TimeoutExpired:
        if timer_stopped:
            timer_stopped.set()
        print("\r" + " " * 150 + "\r", end='', flush=True)
        print(f"{book_prefix}", end='', flush=True)
        return False, f"Timeout after {timeout_seconds} seconds", asin

    except Exception as e:
        return False, str(e), asin


def run_phase_1_alt(extractor_path, content_dir, extracted_books_path, output_k4i,
                    working_dir, config):
    """
    Run Phase 1 (Alternate Method): Decryption & Extraction

    Uses KFXArchiver (any version) to decrypt books one-by-one (via temp copies)
    and write decrypted kfx-zip files to extracted_books_path.

    Args:
        extractor_path: Full path to the KFXArchiver executable (any version)
        content_dir: Kindle content directory (source of .azw books)
        extracted_books_path: Output folder for decrypted kfx-zip files
        output_k4i: Path for the .k4i file (passed to tool, may not be used)
        working_dir: Working directory for logs, history, temp files
        config: Configuration dict

    Returns:
        dict with keys:
            - success: bool
            - stats: dict with extraction statistics
            - kfx_zip_files: list of paths to generated kfx-zip files
    """
    script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    user_home = os.path.expanduser("~")

    # Ensure output folder exists before running
    os.makedirs(extracted_books_path, exist_ok=True)

    # Find Kindle.exe location
    kindle_dir, needs_temp_copy = find_kindle_exe()

    if not kindle_dir:
        raise FileNotFoundError("Kindle for PC installation not found.")

    temp_copy_created = False

    # Initialise extraction statistics
    extraction_stats = {
        'total': 0,
        'success': 0,
        'failed': 0,
        'skipped': 0,
        'failed_books': [],       # Books that failed due to tool errors
        'skipped_books': [],
        'format_failed_books': [] # KF8/MOBI books detected by format mismatch
    }

    # Load config settings
    enable_raw_logs = config.get('enable_raw_logs', False) if config else False

    # Initialise RAW log if enabled
    raw_log_path = None
    if enable_raw_logs:
        raw_log_path = get_raw_log_path(working_dir, 'extraction')
        write_raw_log_header(raw_log_path)   # reads Kindle version + mode from config
        extraction_logs_dir = os.path.join(working_dir, "Logs", "01_Extraction")
        cleanup_old_raw_logs(extraction_logs_dir, keep_count=10)

    try:
        # Create temporary Kindle copy if needed
        if needs_temp_copy:
            kindle_dir = create_temp_kindle_copy(kindle_dir)
            temp_copy_created = True

        # Scan for book folders
        print_step("Scanning for book folders...")
        book_folders = scan_kindle_content_directory(content_dir)

        if not book_folders:
            print_warn("No book folders found in content directory")
            print_error("No books found in content directory!")
            return {
                'success': False,
                'stats': extraction_stats,
                'kfx_zip_files': []
            }

        extraction_stats['total'] = len(book_folders)
        print_ok(f"Found {len(book_folders)} book folder(s)")
        print()

        # Load history
        processed_asins = load_book_history(working_dir)

        # Check for previously processed books
        previously_processed = [asin for asin, _, _ in book_folders if asin in processed_asins]

        if previously_processed:
            print_step(f"Found {len(previously_processed)} previously processed book(s)")
            print("Options:")
            print("  [A] Process All  - Re-process everything including previously processed")
            print("  [N] Process New  - Skip previously processed books (recommended) [DEFAULT]")
            print("  [Q] Quit         - Exit the script")
            print()

            choice = timed_choice_input(
                "Your choice (A/N/Q) [N]: ",
                valid_choices=['A', 'N', 'Q'],
                default='N',
                timeout=10
            )

            if choice == 'N':
                skipped = [(asin, title, "Previously processed")
                           for asin, _, title in book_folders
                           if asin in processed_asins]
                extraction_stats['skipped_books'] = skipped
                extraction_stats['skipped'] = len(skipped)

                book_folders = [(asin, path, title) for asin, path, title in book_folders
                                if asin not in processed_asins]
                print_ok(f"Processing {len(book_folders)} new book(s)")
                print()
            elif choice == 'A':
                print()
                print_ok("Will process all books including previously processed")
                processed_asins.clear()
                print()
            elif choice == 'Q':
                print()
                return {
                    'success': False,
                    'stats': extraction_stats,
                    'kfx_zip_files': []
                }

        if not book_folders:
            print_ok("No new books to process - all books have been previously processed")
            print()
            return {
                'success': True,
                'stats': extraction_stats,
                'kfx_zip_files': []
            }

        # Process each book individually
        print_step(f"Decrypting {len(book_folders)} book(s) to kfx-zip format...")
        print()

        generated_kfx_zip_files = []

        for idx, (asin, book_folder, book_title) in enumerate(book_folders, 1):
            folder_name = os.path.basename(book_folder)

            book_prefix = f"[{idx}/{len(book_folders)}] {folder_name}..."

            print(f"{book_prefix}", end='', flush=True)

            success, error_msg, _ = extract_single_book_alt(
                extractor_path, kindle_dir, book_folder, extracted_books_path,
                output_k4i, asin, asin,
                working_dir=working_dir, raw_log_path=raw_log_path,
                book_prefix=book_prefix
            )

            if success:
                print(" [OK] +")
                extraction_stats['success'] += 1

                # Record the generated kfx-zip file
                kfx_zip_file = find_kfx_zip_for_book(extracted_books_path, asin)
                if kfx_zip_file:
                    generated_kfx_zip_files.append(kfx_zip_file)

                # Update history so we don't re-process on the next run
                append_to_history(working_dir, asin)

            else:
                # Check if failure was due to KF8/MOBI format mismatch
                is_format_mismatch = error_msg and "KF8/MOBI" in error_msg
                if is_format_mismatch:
                    print(f" [KF8/MOBI FORMAT] - Keys saved to .k4i, not decryptable by KFXArchiver")
                    extraction_stats['format_failed_books'].append(
                        (asin, asin, error_msg if error_msg else "KF8/MOBI format")
                    )
                    # Do NOT count toward 'failed' or write to Failed-Books.txt
                    # These books can still be decrypted by DeDRM plugin via dedrm.json
                else:
                    print(f" [ERROR] x FAILED")
                    extraction_stats['failed'] += 1

                    failed_book_title = asin
                    extraction_stats['failed_books'].append(
                        (asin, failed_book_title, error_msg if error_msg else "Unknown error")
                    )
                    append_to_failed_books(working_dir, failed_book_title, asin)

        print()

        # Cleanup temp_extraction folder after all books processed
        temp_extraction_dir = os.path.join(working_dir, "temp_extraction")
        if os.path.exists(temp_extraction_dir):
            print_step("Cleaning up temporary extraction folder...")
            success = rmtree_with_retry(temp_extraction_dir, max_retries=5, retry_delay=0.5)
            if success:
                print_ok("Temporary extraction folder cleaned up successfully")
            else:
                print_warn("Failed to clean up temporary extraction folder - you may need to delete it manually")
            print()

        # Remove extractor copy from Kindle folder
        extractor_in_kindle = os.path.join(kindle_dir, os.path.basename(extractor_path))
        if os.path.exists(extractor_in_kindle):
            try:
                os.remove(extractor_in_kindle)
            except Exception:
                pass

        # Show RAW log location if enabled
        if enable_raw_logs and raw_log_path:
            print_step("Raw debug log saved to:")
            print(f"      {raw_log_path}")

        print()

        overall_success = extraction_stats['success'] > 0

        return {
            'success': overall_success,
            'stats': extraction_stats,
            'kfx_zip_files': generated_kfx_zip_files
        }

    except Exception as e:
        print_error(f"Alternate extractor failed: {e}")
        return {
            'success': False,
            'stats': extraction_stats,
            'kfx_zip_files': []
        }

    finally:
        if temp_copy_created:
            cleanup_temp_kindle_copy(kindle_dir)


def run_phase_1_alt_retry(extractor_path, content_dir, failed_asins, extracted_books_path,
                          output_k4i, working_dir, config):
    """
    Retry failed books from Phase 1 (standard) using the alternate KFXArchiver method.

    This is called automatically after Phase 3 (standard import) completes, when
    extraction_stats contains failed books AND the KFXArchiver executable is available.

    Key differences from run_phase_1_alt:
    - Only processes books whose ASIN is in failed_asins (in-memory list from current run)
    - No interactive prompt (A/N/Q) - retries run automatically without user input
    - On success: removes book from Failed-Books.txt (it was written there by Phase 1 standard)
    - On failure: book stays in Failed-Books.txt (already written by Phase 1 standard)

    Args:
        extractor_path: Full path to the KFXArchiver executable (any version)
        content_dir: Kindle content directory (source of .azw books)
        failed_asins: List of ASIN strings that failed during Phase 1 standard
        extracted_books_path: Output folder for decrypted kfx-zip files
        output_k4i: Path for the .k4i file (passed to tool)
        working_dir: Working directory for logs, history, temp files
        config: Configuration dict

    Returns:
        dict with keys:
            - success: bool  (True if at least one book was successfully retried)
            - stats: dict with retry statistics
            - kfx_zip_files: list of paths to newly generated kfx-zip files
    """
    # Ensure output folder exists
    os.makedirs(extracted_books_path, exist_ok=True)

    # Find Kindle.exe location
    kindle_dir, needs_temp_copy = find_kindle_exe()

    if not kindle_dir:
        print_error("Kindle for PC installation not found - cannot run alt retry.")
        return {
            'success': False,
            'stats': {
                'total': len(failed_asins),
                'success': 0,
                'failed': len(failed_asins),
                'skipped': 0,
                'failed_books': [],
                'skipped_books': []
            },
            'kfx_zip_files': []
        }

    temp_copy_created = False

    retry_stats = {
        'total': len(failed_asins),
        'success': 0,
        'failed': 0,
        'skipped': 0,
        'failed_books': [],
        'skipped_books': []
    }

    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, 'extraction')
        write_raw_log_header(raw_log_path)   # reads Kindle version + mode from config
        extraction_logs_dir = os.path.join(working_dir, "Logs", "01_Extraction")
        cleanup_old_raw_logs(extraction_logs_dir, keep_count=10)

    try:
        if needs_temp_copy:
            kindle_dir = create_temp_kindle_copy(kindle_dir)
            temp_copy_created = True

        # Scan ALL book folders, then filter to only the failed ASINs
        all_book_folders = scan_kindle_content_directory(content_dir)
        failed_asin_set = set(failed_asins)
        book_folders = [(asin, path, title)
                        for asin, path, title in all_book_folders
                        if asin in failed_asin_set]

        if not book_folders:
            print_warn("None of the failed books were found in the content directory.")
            return {
                'success': False,
                'stats': retry_stats,
                'kfx_zip_files': []
            }

        retry_stats['total'] = len(book_folders)
        print_ok(f"Found {len(book_folders)} failed book(s) to retry with alternate method")
        print()

        extractor_name = os.path.basename(extractor_path)
        print_step(f"Retrying {len(book_folders)} book(s) using {extractor_name}...")
        print()

        generated_kfx_zip_files = []

        for idx, (asin, book_folder, book_title) in enumerate(book_folders, 1):
            folder_name = os.path.basename(book_folder)

            book_prefix = f"  [{idx}/{len(book_folders)}] {folder_name}..."

            print(f"{book_prefix}", end='', flush=True)

            success, error_msg, _ = extract_single_book_alt(
                extractor_path, kindle_dir, book_folder, extracted_books_path,
                output_k4i, asin, asin,
                working_dir=working_dir, raw_log_path=raw_log_path,
                book_prefix=book_prefix
            )

            if success:
                print(" [OK] +")
                retry_stats['success'] += 1

                kfx_zip_file = find_kfx_zip_for_book(extracted_books_path, asin)
                if kfx_zip_file:
                    generated_kfx_zip_files.append(kfx_zip_file)

                # Write to history so future runs skip this book
                append_to_history(working_dir, asin)

                # Remove from Failed-Books.txt - it recovered via alt method
                remove_from_failed_books(working_dir, asin)

            else:
                print(f" [STILL FAILED] x")
                retry_stats['failed'] += 1
                retry_stats['failed_books'].append(
                    (asin, asin, error_msg if error_msg else "Unknown error")
                )
                # Book stays in Failed-Books.txt (already written by Phase 1 standard)

        print()

        # Cleanup temp_extraction folder
        temp_extraction_dir = os.path.join(working_dir, "temp_extraction")
        if os.path.exists(temp_extraction_dir):
            print_step("Cleaning up temporary extraction folder...")
            cleanup_ok = rmtree_with_retry(temp_extraction_dir, max_retries=5, retry_delay=0.5)
            if cleanup_ok:
                print_ok("Temporary extraction folder cleaned up successfully")
            else:
                print_warn("Failed to clean up temp_extraction folder - delete manually if needed")
            print()

        # Remove extractor copy from Kindle folder
        extractor_in_kindle = os.path.join(kindle_dir, os.path.basename(extractor_path))
        if os.path.exists(extractor_in_kindle):
            try:
                os.remove(extractor_in_kindle)
            except Exception:
                pass

        if enable_raw_logs and raw_log_path:
            print_step("Raw debug log saved to:")
            print(f"      {raw_log_path}")

        print()

        return {
            'success': retry_stats['success'] > 0,
            'stats': retry_stats,
            'kfx_zip_files': generated_kfx_zip_files
        }

    except Exception as e:
        print_error(f"Alt retry failed with exception: {e}")
        return {
            'success': False,
            'stats': retry_stats,
            'kfx_zip_files': []
        }

    finally:
        if temp_copy_created:
            cleanup_temp_kindle_copy(kindle_dir)
