import hashlib
import itertools
import os
import platform
import random
import time

# 6/20/2022 12AM
BASE_EPOCH = 1655683200
VERSION = 0

# Number of bits
TIMESTAMP_BITS = 32
WORKER_ID_BITS = 21
SEQUENCE_BITS = 10
VERSION_BITS = 1

# Bit shits (left)
TIMESTAMP_SHIFT = 32
WORKER_ID_SHIFT = 11
SEQUENCE_SHIFT = 1
VERSION_SHIFT = 0

SEQUENCE_MAX = (2**SEQUENCE_BITS) - 1
_sequence_start = None
SEQUENCE_COUNTER = itertools.count()
_worker_hash = None
_current_timestamp = None  # Track current second for overflow detection
_sequence_overflow = 0  # Increments when >1024 IDs generated in one second
_sequence_count = 0  # Count IDs in current second

# prefix
ACTION_PREFIX = "act"
AI_SUGGESTION_PREFIX = "as"
ARTIFACT_PREFIX = "a"
AWS_SECRET_PARAMETER_PREFIX = "asp"
BITWARDEN_CREDIT_CARD_DATA_PARAMETER_PREFIX = "bccd"
BITWARDEN_LOGIN_CREDENTIAL_PARAMETER_PREFIX = "blc"
BITWARDEN_SENSITIVE_INFORMATION_PARAMETER_PREFIX = "bsi"
CREDENTIAL_ONEPASSWORD_PARAMETER_PREFIX = "opp"
CREDENTIAL_AZURE_VAULT_PARAMETER_PREFIX = "azcp"
CREDENTIAL_PARAMETER_PREFIX = "cp"
CREDENTIAL_PREFIX = "cred"
DEBUG_SESSION_PREFIX = "ds"
FOLDER_PREFIX = "fld"
BROWSER_PROFILE_PREFIX = "bp"
ORGANIZATION_BITWARDEN_COLLECTION_PREFIX = "obc"
TASK_V2_ID = "tsk_v2"
THOUGHT_ID = "ot"
ORGANIZATION_AUTH_TOKEN_PREFIX = "oat"
ORG_PREFIX = "o"
OUTPUT_PARAMETER_PREFIX = "op"
PERSISTENT_BROWSER_SESSION_ID = "pbs"
SCRIPT_FILE_PREFIX = "sf"
SCRIPT_REVISION_PREFIX = "sr"
SCRIPT_PREFIX = "s"
SCRIPT_BLOCK_PREFIX = "sb"
STEP_PREFIX = "stp"
TASK_GENERATION_PREFIX = "tg"
TASK_PREFIX = "tsk"
TASK_RUN_PREFIX = "tr"
TOTP_CODE_PREFIX = "totp"
USER_PREFIX = "u"
WORKFLOW_PARAMETER_PREFIX = "wp"
WORKFLOW_PERMANENT_ID_PREFIX = "wpid"
WORKFLOW_PREFIX = "w"
WORKFLOW_RUN_BLOCK_PREFIX = "wrb"
WORKFLOW_RUN_PREFIX = "wr"
WORKFLOW_SCRIPT_PREFIX = "ws"
WORKFLOW_TEMPLATE_PREFIX = "wt"
ORGANIZATION_BILLING_PREFIX = "ob"
WORKFLOW_COPILOT_CHAT_PREFIX = "wcc"
WORKFLOW_COPILOT_CHAT_MESSAGE_PREFIX = "wccm"
SCRIPT_FALLBACK_EPISODE_PREFIX = "sfe"
WORKFLOW_SCHEDULE_PREFIX = "wfs"


def generate_workflow_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_PREFIX}_{int_id}"


def generate_workflow_permanent_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_PERMANENT_ID_PREFIX}_{int_id}"


def generate_workflow_run_block_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_RUN_BLOCK_PREFIX}_{int_id}"


def generate_workflow_run_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_RUN_PREFIX}_{int_id}"


def generate_workflow_script_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_SCRIPT_PREFIX}_{int_id}"


def generate_workflow_template_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_TEMPLATE_PREFIX}_{int_id}"


def generate_aws_secret_parameter_id() -> str:
    int_id = generate_id()
    return f"{AWS_SECRET_PARAMETER_PREFIX}_{int_id}"


def generate_workflow_parameter_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_PARAMETER_PREFIX}_{int_id}"


def generate_output_parameter_id() -> str:
    int_id = generate_id()
    return f"{OUTPUT_PARAMETER_PREFIX}_{int_id}"


def generate_bitwarden_login_credential_parameter_id() -> str:
    int_id = generate_id()
    return f"{BITWARDEN_LOGIN_CREDENTIAL_PARAMETER_PREFIX}_{int_id}"


def generate_bitwarden_sensitive_information_parameter_id() -> str:
    int_id = generate_id()
    return f"{BITWARDEN_SENSITIVE_INFORMATION_PARAMETER_PREFIX}_{int_id}"


def generate_bitwarden_credit_card_data_parameter_id() -> str:
    int_id = generate_id()
    return f"{BITWARDEN_CREDIT_CARD_DATA_PARAMETER_PREFIX}_{int_id}"


def generate_onepassword_credential_parameter_id() -> str:
    int_id = generate_id()
    return f"{CREDENTIAL_ONEPASSWORD_PARAMETER_PREFIX}_{int_id}"


def generate_azure_vault_credential_parameter_id() -> str:
    int_id = generate_id()
    return f"{CREDENTIAL_AZURE_VAULT_PARAMETER_PREFIX}_{int_id}"


def generate_organization_auth_token_id() -> str:
    int_id = generate_id()
    return f"{ORGANIZATION_AUTH_TOKEN_PREFIX}_{int_id}"


def generate_org_id() -> str:
    int_id = generate_id()
    return f"{ORG_PREFIX}_{int_id}"


def generate_task_id() -> str:
    int_id = generate_id()
    return f"{TASK_PREFIX}_{int_id}"


def generate_step_id() -> str:
    int_id = generate_id()
    return f"{STEP_PREFIX}_{int_id}"


def generate_artifact_id() -> str:
    int_id = generate_id()
    return f"{ARTIFACT_PREFIX}_{int_id}"


def generate_user_id() -> str:
    int_id = generate_id()
    return f"{USER_PREFIX}_{int_id}"


def generate_task_generation_id() -> str:
    int_id = generate_id()
    return f"{TASK_GENERATION_PREFIX}_{int_id}"


def generate_ai_suggestion_id() -> str:
    int_id = generate_id()
    return f"{AI_SUGGESTION_PREFIX}_{int_id}"


def generate_totp_code_id() -> str:
    int_id = generate_id()
    return f"{TOTP_CODE_PREFIX}_{int_id}"


def generate_action_id() -> str:
    int_id = generate_id()
    return f"{ACTION_PREFIX}_{int_id}"


def generate_task_v2_id() -> str:
    int_id = generate_id()
    return f"{TASK_V2_ID}_{int_id}"


def generate_thought_id() -> str:
    int_id = generate_id()
    return f"{THOUGHT_ID}_{int_id}"


def generate_persistent_browser_session_id() -> str:
    int_id = generate_id()
    return f"{PERSISTENT_BROWSER_SESSION_ID}_{int_id}"


def generate_browser_profile_id() -> str:
    int_id = generate_id()
    return f"{BROWSER_PROFILE_PREFIX}_{int_id}"


def generate_task_run_id() -> str:
    int_id = generate_id()
    return f"{TASK_RUN_PREFIX}_{int_id}"


def generate_credential_parameter_id() -> str:
    int_id = generate_id()
    return f"{CREDENTIAL_PARAMETER_PREFIX}_{int_id}"


def generate_credential_id() -> str:
    int_id = generate_id()
    return f"{CREDENTIAL_PREFIX}_{int_id}"


def generate_debug_session_id() -> str:
    int_id = generate_id()
    return f"{DEBUG_SESSION_PREFIX}_{int_id}"


def generate_folder_id() -> str:
    int_id = generate_id()
    return f"{FOLDER_PREFIX}_{int_id}"


def generate_organization_bitwarden_collection_id() -> str:
    int_id = generate_id()
    return f"{ORGANIZATION_BITWARDEN_COLLECTION_PREFIX}_{int_id}"


def generate_script_id() -> str:
    int_id = generate_id()
    return f"{SCRIPT_PREFIX}_{int_id}"


def generate_script_revision_id() -> str:
    int_id = generate_id()
    return f"{SCRIPT_REVISION_PREFIX}_{int_id}"


def generate_script_file_id() -> str:
    int_id = generate_id()
    return f"{SCRIPT_FILE_PREFIX}_{int_id}"


def generate_script_block_id() -> str:
    int_id = generate_id()
    return f"{SCRIPT_BLOCK_PREFIX}_{int_id}"


def generate_billing_id() -> str:
    int_id = generate_id()
    return f"{ORGANIZATION_BILLING_PREFIX}_{int_id}"


def generate_workflow_copilot_chat_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_COPILOT_CHAT_PREFIX}_{int_id}"


def generate_workflow_copilot_chat_message_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_COPILOT_CHAT_MESSAGE_PREFIX}_{int_id}"


def generate_script_fallback_episode_id() -> str:
    int_id = generate_id()
    return f"{SCRIPT_FALLBACK_EPISODE_PREFIX}_{int_id}"


def generate_workflow_schedule_id() -> str:
    int_id = generate_id()
    return f"{WORKFLOW_SCHEDULE_PREFIX}_{int_id}"


############# Helper functions below ##############
def generate_id() -> int:
    """
    generate a 64-bit int ID

    Handles >1024 IDs per second by adding overflow counter to worker hash.
    Safe for asyncio (no threading concerns - function is atomic).
    """
    global _current_timestamp, _sequence_overflow, _sequence_count

    create_at = current_time() - BASE_EPOCH
    sequence = _increment_and_get_sequence()

    if _current_timestamp != create_at:
        _current_timestamp = create_at
        _sequence_overflow = 0
        _sequence_count = 0

    _sequence_count += 1

    if _sequence_count > SEQUENCE_MAX + 1:
        _sequence_overflow = (_sequence_count - 1) // (SEQUENCE_MAX + 1)

    time_part = _mask_shift(create_at, TIMESTAMP_BITS, TIMESTAMP_SHIFT)
    worker_part = _mask_shift(_get_worker_hash() + _sequence_overflow, WORKER_ID_BITS, WORKER_ID_SHIFT)
    sequence_part = _mask_shift(sequence, SEQUENCE_BITS, SEQUENCE_SHIFT)
    version_part = _mask_shift(VERSION, VERSION_BITS, VERSION_SHIFT)

    return time_part | worker_part | sequence_part | version_part


def _increment_and_get_sequence() -> int:
    global _sequence_start
    if _sequence_start is None:
        _sequence_start = random.randint(0, SEQUENCE_MAX)

    return (_sequence_start + next(SEQUENCE_COUNTER)) & SEQUENCE_MAX


def current_time() -> int:
    return int(time.time())


def current_time_ms() -> int:
    return int(time.time() * 1000)


def _mask_shift(value: int, mask_bits: int, shift_bits: int) -> int:
    return (value & ((1 << mask_bits) - 1)) << shift_bits


def _get_worker_hash() -> int:
    global _worker_hash
    if _worker_hash is None:
        _worker_hash = _generate_worker_hash()
    return _worker_hash


def _generate_worker_hash() -> int:
    worker_identity = f"{platform.node()}:{os.getpid()}"
    return int(hashlib.md5(worker_identity.encode()).hexdigest()[-15:], 16)
