import glob
import os
from pathlib import Path

import click

from easy_dataset_share import canary, hashing, robots, tos, zipping


def _normalize_dir_path(dir_path: str) -> str:
    """Normalize directory path to remove leading slash and ensure proper formatting."""
    # Convert to Path object and resolve to absolute path
    path = Path(dir_path).resolve()
    # Convert back to string and ensure no leading slash issues
    return str(path)


def _verify_canary_removal(path: str, verbose: bool) -> dict:
    """Helper to verify canary removal and print appropriate messages."""
    verification_result = canary.verify_canary_dataset(path)
    if verification_result["is_valid"]:
        click.echo(f"⚠️  Warning: Canaries may still be present in {path}")
        if verbose:
            click.echo(f"   Found {verification_result['canary_files_found']} canary files")
            click.echo(f"   Detection confidence: {verification_result['detection_confidence']:.2f}")
    else:
        click.echo(f"✅ Verification confirmed: All canaries successfully removed from {path}")
    return verification_result


def _prompt_if_default(value: str, prompt_message: str, default_value: str) -> str:
    """Prompt user if the value is the default placeholder value."""
    if value == default_value:
        return click.prompt(prompt_message, default=default_value)
    return value


@click.group()
def cli() -> None:
    """Making responsible dataset sharing easy!"""
    pass


@cli.command()
@click.argument("dir", type=click.Path(exists=True, file_okay=False, dir_okay=True))
@click.option(
    "--password",
    "-p",
    default=None,
    help="Password for protection (default: no encryption)",
)
@click.option("--output", "-o", help="Output file path (optional)")
@click.option(
    "--allow-crawling",
    "-a",
    is_flag=True,
    help="Allow crawling (default is to disallow all)",
)
@click.option("--user-agent", "-u", default="*", help="User-agent to target (default: *)")
@click.option(
    "--num-canary-files",
    "-c",
    default=1,
    help="Number of canary files to create",
)
@click.option(
    "--embed-canaries",
    "-e",
    default=False,
    is_flag=True,
    help="Embed canaries in existing files (default is to create canary files)",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def magic_protect_dir(
    dir: str,
    password: str | None,
    output: str | None,
    allow_crawling: bool,
    user_agent: str,
    num_canary_files: int,
    embed_canaries: bool,
    verbose: bool,
) -> None:
    """Zip a directory and password protect it in one step."""
    try:
        # Normalize directory path
        dir = _normalize_dir_path(dir)

        # Hash files before adding canaries
        before_hash_result = hashing.hash_directory(dir, verbose=verbose)
        click.echo(f"📊 Dataset hash (before canaries): {before_hash_result['directory_hash']}")
        if verbose:
            click.echo(f"📁 Files hashed: {before_hash_result['total_files']}")

        # Validate JSON/JSONL files before processing
        try:
            canary.validate_json_files(dir, verbose)
        except ValueError as e:
            click.echo(f"❌ {e}", err=True)
            raise click.Abort()

        # Generate robots.txt in the directory
        robots_path = os.path.join(dir, "robots.txt")
        robots.generate_robots_txt(disallow_all=not allow_crawling, user_agent=user_agent)
        robots.save_robots_txt(robots_path, verbose)
        click.echo(f"✅ Added robots.txt to {dir}")

        # Add canary files to the directory
        canary_string, canary_files = canary.create_canary_files_from_dataset(dir, "*", num_canary_files, verbose)
        click.echo(f"✅ Added {len(canary_files)} canary files to {dir}")
        if embed_canaries:
            # add canaries to existing files
            canary.insert_canaries_into_files(dir, canary_string, verbose)

        # Hash files after adding canaries
        after_hash_result = hashing.hash_directory(dir, verbose=verbose)
        # Show comparison
        if verbose and before_hash_result["directory_hash"] != after_hash_result["directory_hash"]:
            click.echo("⚠️  Directory hash changed - this may indicate canary files were included in hashing")

        result = zipping.zip_and_password_protect(dir, password, output, verbose)
        click.echo(f"✅ Successfully protected {dir} -> {result}")

        # Show the unprotect command
        click.echo("\n📋 To unprotect this archive, run:")
        if password:
            click.echo(f"   magic-unprotect-dir {result} -p {password} -rc")
        else:
            click.echo(f"   magic-unprotect-dir {result} -rc")
    except Exception as e:
        click.echo(f"❌ Error protecting directory: {e}", err=True)
        raise click.Abort()


@cli.command()
@click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
@click.option(
    "--password",
    "-p",
    required=False,
    default=None,
    help="Password for decryption",
)
@click.option("--output-dir", "-o", help="Output directory (optional)")
@click.option(
    "--remove-canaries",
    "-rc",
    is_flag=True,
    help="Remove canary files after extraction",
)
@click.option(
    "--canary-pattern",
    "-cp",
    default="dataset_entry_*.jsonl",
    help="Pattern to match canary files",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def magic_unprotect_dir(
    file: str,
    password: str | None,
    output_dir: str | None,
    remove_canaries: bool,
    canary_pattern: str,
    verbose: bool,
) -> None:
    """Decrypt and extract a protected directory in one step."""
    try:
        result = zipping.unzip_and_decrypt(file, password, output_dir, verbose)
        click.echo(f"✅ Successfully extracted {file} -> {result}")

        # Normalize the result path
        result = _normalize_dir_path(result)

        # Remove canary files from the extracted directory if requested
        if remove_canaries:
            # Hash files before removing canaries
            before_hash_result = hashing.hash_directory(result, verbose=verbose)
            if verbose:
                click.echo(f"📊 Hash before removing canaries: {before_hash_result['directory_hash']}")

            # First, validate that the canary string matches what's expected
            try:
                canary_string = canary.generate_canary_string_from_dataset(result)
                # Check if canary files exist and contain the expected canary string
                canary_files = canary.detect_canary_files(result, canary_pattern)
                if canary_files:
                    # Verify at least one canary file contains the expected string
                    canary_found = False
                    for canary_file in canary_files:
                        if canary.check_canary_exists(canary_file, canary_string, verbose):
                            canary_found = True
                            break

                    if not canary_found:
                        click.echo(
                            f"❌ Canary string mismatch in {result}. " f"Canary files may not be removed correctly.",
                            err=True,
                        )
                        # Don't exit with error, but warn the user

                canary.remove_canary_files(root_dir=result, canary_pattern=canary_pattern)
                canary.remove_canaries_from_files(result, canary_string, verbose)
                click.echo(f"✅ Removed canary files from {result}")

                # Hash files after removing canaries
                after_hash_result = hashing.hash_directory(result, verbose=verbose)

                # Always show the final hash after canary removal
                click.echo(f"📊 Dataset hash: {after_hash_result['directory_hash']}")

                # Show verification details only in verbose mode
                if verbose:
                    click.echo(f"📁 Files hashed: {after_hash_result['total_files']}")
                    if before_hash_result["directory_hash"] == after_hash_result["directory_hash"]:
                        click.echo("✅ Hash verification passed - dataset integrity maintained")
                    else:
                        click.echo("⚠️  Warning: Hash mismatch - dataset may have been modified")
                # Verify the cleaning was successful
                _verify_canary_removal(result, verbose)

            except Exception as e:
                click.echo(f"❌ Error processing canaries: {e}", err=True)
                # Exit with error code for canary processing failures
                raise
        else:
            click.echo(f"ℹ️  Canary files preserved in {result} (use --remove-canaries to clean them)")

    except Exception as e:
        click.echo(f"❌ Error extracting protected file: {e}", err=True)
        # Exit with error code for any unprotect failures
        raise click.Abort()


# get canary string for dir
@cli.command()
@click.argument("dir_path", type=click.Path(exists=True, file_okay=False, dir_okay=True))
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def get_canary_string(dir_path: str, verbose: bool) -> None:
    """Get the canary string for a directory."""
    # Normalize directory path
    dir_path = _normalize_dir_path(dir_path)
    canary_string = canary.generate_canary_string_from_dataset(dir_path)
    click.echo(f"✅ Canary string: {canary_string}")


@cli.command()
@click.argument("root", type=click.Path(exists=True, file_okay=True, dir_okay=True))
@click.option("--pattern", "-p", default="*", help="Pattern to match existing files")
@click.option(
    "--num-canary-files",
    "-f",
    default=1,
    help="Number of canary files to create",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def add_canary(root: str, pattern: str, num_canary_files: int, verbose: bool) -> None:
    """Add canary files to a dataset for LLM training detection."""
    try:
        # Normalize root directory path
        root = _normalize_dir_path(root)

        # Hash files before adding canaries
        before_hash_result = hashing.hash_directory(root, verbose=verbose)
        if verbose:
            click.echo(f"📊 Hash before adding canaries: {before_hash_result['directory_hash']}")
            click.echo(f"📁 Files hashed: {before_hash_result['total_files']}")

        # Create canary files
        canary_string, canary_files = canary.create_canary_files_from_dataset(root, pattern, num_canary_files, verbose)
        click.echo(f"✅ Created {len(canary_files)} canary files")

        after_hash_result = hashing.hash_directory(root, verbose=verbose)
        if verbose:
            click.echo(f"📊 Hash after adding canaries: {after_hash_result['directory_hash']}")
            click.echo(f"📁 Files hashed: {after_hash_result['total_files']}")
    except Exception as e:
        click.echo(f"❌ Error adding canary files: {e}", err=True)


@cli.command()
@click.argument("root", type=click.Path(exists=True, file_okay=False, dir_okay=True))
@click.option(
    "--canary-pattern",
    "-cp",
    default="dataset_entry_*.jsonl",
    help="Pattern to match canary files",
)
@click.option(
    "--files-only",
    is_flag=True,
    default=False,
    help="Only remove canary files, not embedded canaries",
)
@click.option(
    "--embedded-only",
    is_flag=True,
    default=False,
    help="Only remove embedded canaries, not canary files",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def remove_canary(
    root: str,
    canary_pattern: str,
    files_only: bool,
    embedded_only: bool,
    verbose: bool,
) -> None:
    """Remove canary files and/or embedded canaries from a dataset."""
    try:
        # Normalize root directory path
        root = _normalize_dir_path(root)

        click.echo(
            f"[DEBUG] remove_canary: operating on absolute path: {os.path.abspath(root)}",
            err=True,
        )
        before_files = glob.glob(os.path.join(root, canary_pattern))
        click.echo(
            f"[DEBUG] Files matching canary pattern before removal: {before_files}",
            err=True,
        )

        # Hash files before removing canaries
        before_hash_result = hashing.hash_directory(root, verbose=verbose)
        if verbose:
            click.echo(f"📊 Hash before removing canaries: {before_hash_result['directory_hash']}")
            click.echo(f"📁 Files hashed: {before_hash_result['total_files']}")

        # Verify canaries exist before removal
        pre_verification = canary.verify_canary_dataset(root)
        if not pre_verification["is_valid"]:
            click.echo(f"ℹ️  No canaries found in {root}")
            return

        # Determine which removal(s) to perform
        if files_only and embedded_only:
            click.echo(
                "❌ Cannot specify both --files-only and --embedded-only.",
                err=True,
            )
            return

        canary_strings = []
        if not files_only:
            # Need canary strings for embedded removal - extract from all canary files
            canary_files = canary.detect_canary_files(root, canary_pattern)
            for cf in canary_files:
                cs = canary.extract_canary_string_from_content(cf)
                if cs:
                    canary_strings.append(cs)
            if not canary_strings:
                raise RuntimeError("Failed to extract canary strings from existing canary files.")

        if not embedded_only:
            canary.remove_canary_files(root_dir=root, canary_pattern=canary_pattern, verbose=verbose)
        if not files_only:
            for cs in canary_strings:
                canary.remove_canaries_from_files(root, cs, verbose)

        # Hash files after removing canaries
        after_hash_result = hashing.hash_directory(root, verbose=verbose)

        # Always show the final hash
        click.echo(f"📊 Dataset hash: {after_hash_result['directory_hash']}")

        if verbose:
            click.echo(f"📁 Files hashed: {after_hash_result['total_files']}")
            # Verify hashes match if only canaries were removed
            if before_hash_result["directory_hash"] == after_hash_result["directory_hash"]:
                click.echo("✅ Hash verification passed - dataset integrity maintained")
            else:
                click.echo("⚠️  Warning: Hash mismatch - dataset may have been modified beyond canary removal")
        # Verify the cleaning was successful
        _verify_canary_removal(root, verbose)

        after_files = glob.glob(os.path.join(root, canary_pattern))
        click.echo(
            f"[DEBUG] Files matching canary pattern after removal: {after_files}",
            err=True,
        )

    except Exception as e:
        click.echo(f"❌ Error removing canaries: {e}", err=True)


@cli.command()
@click.argument("path", type=click.Path(exists=True, file_okay=True, dir_okay=True))
@click.option(
    "--allow-crawling",
    "-a",
    is_flag=True,
    help="Allow crawling (default is to disallow all)",
)
@click.option("--user-agent", "-u", default="*", help="User-agent to target (default: *)")
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def add_robots(path: str, allow_crawling: bool, user_agent: str, verbose: bool) -> None:
    """Generate and save a robots.txt file to prevent LLM training."""
    try:
        # Generate robots.txt content
        robots.generate_robots_txt(disallow_all=not allow_crawling, user_agent=user_agent)

        # Save the file
        robots.save_robots_txt(path, verbose)

        click.echo(f"✅ Created robots.txt at: {path}")
        if verbose:
            click.echo(f"🤖 User-agent: {user_agent}")

            if not allow_crawling:
                click.echo("🚫 Disallowing all crawling (prevents LLM training)")
                click.echo("💡 This helps prevent web crawlers from collecting your dataset for training")
            else:
                click.echo("✅ Allowing crawling")
    except Exception as e:
        click.echo(f"❌ Error creating robots.txt: {e}", err=True)


@cli.command()
@click.argument("directory", type=click.Path(exists=True, file_okay=False, dir_okay=True))
@click.option(
    "--organization-name",
    "-on",
    default="Example Corp",
    help="Name of the company",
)
@click.option(
    "--dataset-name",
    "-dn",
    default="Example Dataset",
    help="Name of the dataset",
)
@click.option(
    "--contact-email",
    "-ce",
    default="support@example.com",
    help="Contact email address",
)
@click.option(
    "--effective-date",
    "-ed",
    help="Effective date (YYYY-MM-DD format, default: today)",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
@click.option("--non-interactive", is_flag=True, help="Skip interactive prompts")
def add_tos(
    directory: str,
    organization_name: str,
    dataset_name: str,
    contact_email: str,
    effective_date: str | None,
    verbose: bool,
    non_interactive: bool,
) -> None:
    """Generate and save a terms of service (tos.txt) file in the specified directory."""
    try:
        # Normalize directory path
        directory = _normalize_dir_path(directory)

        # Prompt for important information if using defaults and not in non-interactive mode
        if not non_interactive:
            organization_name = _prompt_if_default(
                organization_name,
                "🏢 Organization/Company name",
                "Example Corp",
            )
            dataset_name = _prompt_if_default(dataset_name, "📊 Dataset name", "Example Dataset")
            contact_email = _prompt_if_default(
                contact_email,
                "📧 Contact email address",
                "support@example.com",
            )

        # Generate tos.txt content
        tos.generate_tos_txt(
            organization_name=organization_name,
            dataset_name=dataset_name,
            contact_email=contact_email,
            effective_date=effective_date,
        )

        # Save the file in the directory
        tos_path = os.path.join(directory, "tos.txt")
        tos.save_tos_txt(tos_path, verbose)

        click.echo(f"✅ Created tos.txt in {directory}")
        if verbose:
            click.echo(f"🏢 Company: {organization_name}")
            click.echo(f"📧 Contact: {contact_email}")

            if effective_date:
                click.echo(f"📅 Effective Date: {effective_date}")
            else:
                click.echo("📅 Effective Date: Today")

    except Exception as e:
        click.echo(f"❌ Error creating tos.txt: {e}", err=True)


@cli.command()
@click.argument("directory", type=click.Path(exists=True, file_okay=False, dir_okay=True))
@click.option(
    "--exclude",
    "-e",
    multiple=True,
    help="Patterns to exclude from hashing (can specify multiple)",
)
@click.option(
    "--verbose",
    "-v",
    is_flag=True,
    default=False,
    help="Enable verbose output",
)
def hash(directory: str, exclude: tuple[str, ...], verbose: bool) -> None:
    """Hash all files in a directory, excluding canary files and other specified patterns."""
    try:
        # Normalize directory path
        directory = _normalize_dir_path(directory)

        # Set up exclude patterns
        exclude_patterns = list(exclude) if exclude else ["dataset_entry_*.jsonl", "robots.txt", "tos.txt"]

        click.echo(f"🔍 Hashing files in: {directory}")
        if verbose:
            click.echo(f"   Excluding patterns: {exclude_patterns}")

        # Hash the directory
        result = hashing.hash_directory(directory, exclude_patterns, verbose)

        click.echo(f"📊 Directory hash: {result['directory_hash']}")

    except Exception as e:
        click.echo(f"❌ Error hashing directory: {e}", err=True)
        raise click.Abort()


if __name__ == "__main__":
    cli()
