#!/usr/bin/env python3
"""
scripts/seed_tsys_credentials.py
=================================
Upserts TSYS TransIT credentials for a specific merchant into the encrypted
credential store (merchant_provider_credentials table).

All values are encrypted with Fernet (AES-128-CBC + HMAC-SHA256) before
being written; plaintext values never reach the database column.

After this script runs the merchant's active_provider_id is pointed at the
TSYS provider config, so the legacy env-var fallback path in factory.py
will no longer be used for that merchant.

Prerequisites
-------------
- CREDENTIAL_ENCRYPTION_KEY must be set in .env (or the environment).
- The merchant row must already exist in the database.

Usage
-----
    # Seed using default values read from .env
    python scripts/seed_tsys_credentials.py --merchant-id 1

    # Override individual values on the command line
    python scripts/seed_tsys_credentials.py \\
        --merchant-id 1 \\
        --tsys-merchant-id 887000003638 \\
        --device-id 88700000363802 \\
        --developer-id 003638G001 \\
        --api-base-url https://stagegw.transnox.com/servlets/TransNox_API_server \\
        --tsep-base-url https://stagegw.transnox.com \\
        --tsys-env test \\
        --user-id TA5972473 \\
        --password 'Dreamztech@2026$'

    # Preview without writing anything
    python scripts/seed_tsys_credentials.py --merchant-id 1 --dry-run

    # Skip updating merchant.active_provider_id
    python scripts/seed_tsys_credentials.py --merchant-id 1 --no-activate

Credential keys stored in DB
-----------------------------
  merchant_id      TSYS merchant identifier (12-20 chars)
  device_id        TSEP / TransIT device identifier (up to 24 chars)
  user_id          User ID for GenerateKey (leave blank if transaction_key is set)
  password         Password for GenerateKey (leave blank if transaction_key is set)
  transaction_key  Long-lived key from GenerateKey; preferred over user_id+password
  developer_id     TSYS developer application ID
  api_base_url     TransIT API base URL
  tsep_base_url    TSEP hosted-fields JS base URL
  tsys_env         "test" or "live"
  webhook_secret   Secret for X-GP-Signature webhook validation (optional)
  store_number     Store number (optional, some terminal flows)
  terminal_number  Terminal number (optional, some terminal flows)
"""

import argparse
import os
import sys

# Ensure the project root is on the Python path so src.* imports work when
# this script is run from any directory.
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _ROOT)

# Load .env before importing settings so env vars are available.
try:
    from dotenv import load_dotenv
    load_dotenv(os.path.join(_ROOT, ".env"))
except ImportError:
    pass  # python-dotenv not installed; rely on env vars being set externally

# Register all ORM models so SQLAlchemy mapper can resolve relationships.
from src.core.database import register_models  # noqa: E402
register_models()

from sqlalchemy import create_engine, select  # noqa: E402
from sqlalchemy.orm import Session  # noqa: E402

from src.core.config import settings  # noqa: E402
from src.apps.payment_providers.crud import (  # noqa: E402
    upsert_credential,
    upsert_merchant_provider_config,
    get_provider_by_slug,
    update_merchant_active_provider,
)
from src.apps.payment_providers.models.payment_provider import PaymentProvider  # noqa: E402

PROVIDER_SLUG = "tsys"
PROVIDER_NAME = "TSYS TransIT"
SENSITIVE_KEYS = {"password", "transaction_key", "webhook_secret"}


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _mask(key: str, value: str) -> str:
    """Return '***' for sensitive keys, the value itself otherwise."""
    if key in SENSITIVE_KEYS:
        return "***" if value else "(empty)"
    return value or "(empty)"


def ensure_tsys_provider(db: Session) -> int:
    """
    Return the payment_providers.id for TSYS, creating the row if it is absent.
    """
    provider = get_provider_by_slug(PROVIDER_SLUG, db)
    if provider:
        print(f"  Found existing provider: id={provider.id}  slug={provider.slug}")
        return provider.id

    new_provider = PaymentProvider(
        name=PROVIDER_NAME,
        slug=PROVIDER_SLUG,
        description="TSYS TransIT + TSEP hosted payment gateway",
        is_active=True,
        is_default=True,
        supported_payment_methods={"card": True, "ach": True},
        onboarding_schema={
            "credentials": [
                {
                    "key": "merchant_id",
                    "label": "Merchant ID",
                    "required": True,
                    "secret": False,
                },
                {
                    "key": "device_id",
                    "label": "Device ID",
                    "required": True,
                    "secret": False,
                },
                {
                    "key": "transaction_key",
                    "label": "Transaction Key",
                    "required": True,
                    "secret": True,
                },
                {
                    "key": "developer_id",
                    "label": "Developer ID",
                    "required": True,
                    "secret": False,
                },
                {
                    "key": "api_base_url",
                    "label": "TransIT API Base URL",
                    "required": True,
                    "secret": False,
                },
                {
                    "key": "tsep_base_url",
                    "label": "TSEP Base URL",
                    "required": True,
                    "secret": False,
                },
                {
                    "key": "tsys_env",
                    "label": "Environment (test / live)",
                    "required": True,
                    "secret": False,
                },
                {
                    "key": "webhook_secret",
                    "label": "Webhook Secret",
                    "required": False,
                    "secret": True,
                },
            ]
        },
    )
    db.add(new_provider)
    db.flush()
    print(f"  Created new provider: id={new_provider.id}  slug={new_provider.slug}")
    return new_provider.id


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def build_arg_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Seed TSYS TransIT credentials for a merchant into the encrypted DB store",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )

    parser.add_argument(
        "--merchant-id",
        type=int,
        required=True,
        metavar="ID",
        help="Primary key of the merchant row to configure",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print the values that would be written without touching the database",
    )
    parser.add_argument(
        "--no-activate",
        dest="activate",
        action="store_false",
        default=True,
        help="Do not update merchant.active_provider_id after seeding credentials",
    )

    # Per-credential overrides — all optional; fall back to .env values.
    cred_group = parser.add_argument_group(
        "credential overrides",
        "Override specific credential values (default: read from .env / TSYS_* env vars)",
    )
    cred_group.add_argument("--tsys-merchant-id",  default=None, metavar="VAL",
                            help="TSYS merchant ID (env: TSYS_MERCHANT_ID)")
    cred_group.add_argument("--device-id",          default=None, metavar="VAL",
                            help="TSEP device ID (env: TSYS_DEVICE_ID)")
    cred_group.add_argument("--transaction-key",    default=None, metavar="VAL",
                            help="Pre-generated transaction key (overrides user-id+password)")
    cred_group.add_argument("--user-id",            default=None, metavar="VAL",
                            help="User ID for GenerateKey (env: TSYS_USER_ID)")
    cred_group.add_argument("--password",           default=None, metavar="VAL",
                            help="Password for GenerateKey (env: TSYS_PASSWORD)")
    cred_group.add_argument("--developer-id",       default=None, metavar="VAL",
                            help="Developer application ID (env: TSYS_DEVELOPER_ID)")
    cred_group.add_argument("--api-base-url",       default=None, metavar="URL",
                            help="TransIT API base URL (env: TSYS_API_BASE_URL)")
    cred_group.add_argument("--tsep-base-url",      default=None, metavar="URL",
                            help="TSEP JS base URL (env: TSYS_TSEP_BASE_URL)")
    cred_group.add_argument("--tsys-env",           default=None, metavar="ENV",
                            help="TSYS environment: test or live (env: TSYS_ENV)")
    cred_group.add_argument("--webhook-secret",     default=None, metavar="VAL",
                            help="Webhook secret for X-GP-Signature (env: TSYS_WEBHOOK_SECRET)")
    cred_group.add_argument("--store-number",       default=None, metavar="VAL",
                            help="Store number (env: TSYS_STORE_NUMBER)")
    cred_group.add_argument("--terminal-number",    default=None, metavar="VAL",
                            help="Terminal number (env: TSYS_TERMINAL_NUMBER)")

    return parser


def main() -> None:
    parser = build_arg_parser()
    args = parser.parse_args()

    # Build the credentials dict — CLI args take priority over env vars.
    credentials: dict[str, str] = {
        "merchant_id":     args.tsys_merchant_id or settings.TSYS_MERCHANT_ID or "",
        "device_id":       args.device_id        or settings.TSYS_DEVICE_ID   or "",
        "user_id":         args.user_id           or settings.TSYS_USER_ID     or "",
        "password":        args.password          or settings.TSYS_PASSWORD    or "",
        "transaction_key": args.transaction_key   or "",
        "developer_id":    args.developer_id      or settings.TSYS_DEVELOPER_ID or "",
        "api_base_url":    args.api_base_url      or settings.TSYS_API_BASE_URL or "",
        "tsep_base_url":   args.tsep_base_url     or settings.TSYS_TSEP_BASE_URL or "",
        "tsys_env":        args.tsys_env          or settings.TSYS_ENV           or "test",
        "webhook_secret":  args.webhook_secret    or settings.TSYS_WEBHOOK_SECRET or "",
        "store_number":    args.store_number      or settings.TSYS_STORE_NUMBER   or "",
        "terminal_number": args.terminal_number   or settings.TSYS_TERMINAL_NUMBER or "",
    }

    # ------------------------------------------------------------------
    # Print preview
    # ------------------------------------------------------------------
    print()
    print("=" * 62)
    print(f"  TSYS Credential Seed  —  merchant_id = {args.merchant_id}")
    print("=" * 62)
    for key, value in credentials.items():
        print(f"  {key:<20}  {_mask(key, value)}")
    print(f"  {'activate provider':<20}  {args.activate}")
    print(f"  {'dry_run':<20}  {args.dry_run}")
    print("=" * 62)
    print()

    if args.dry_run:
        print("DRY RUN — no changes written to the database.\n")
        return

    # ------------------------------------------------------------------
    # Write to DB
    # ------------------------------------------------------------------
    db_url = settings.DATABASE_URL
    engine = create_engine(db_url, echo=False)

    with Session(engine) as db:
        try:
            print("Step 1 — Ensuring payment_providers record exists...")
            provider_id = ensure_tsys_provider(db)

            print(
                f"Step 2 — Upserting merchant_provider_configs "
                f"(merchant={args.merchant_id}, provider={provider_id})..."
            )
            mpc = upsert_merchant_provider_config(
                merchant_id=args.merchant_id,
                provider_id=provider_id,
                onboarding_status="active",
                config_data=None,
                db=db,
            )
            print(f"  merchant_provider_configs.id = {mpc.id}")

            print("Step 3 — Writing encrypted credentials...")
            skipped = 0
            for key, value in credentials.items():
                if not value:
                    print(f"  Skipping  '{key}'  (empty — not written)")
                    skipped += 1
                    continue
                upsert_credential(config_id=mpc.id, key=key, value=value, db=db)
                print(f"  Upserted  '{key}'  →  {_mask(key, value)}")

            if args.activate:
                print(
                    f"Step 4 — Setting merchant.active_provider_id = {provider_id} "
                    f"for merchant {args.merchant_id}..."
                )
                update_merchant_active_provider(args.merchant_id, provider_id, db)

            db.commit()
            print()
            print("=" * 62)
            print("  Credentials seeded successfully.")
            if skipped:
                print(f"  ({skipped} empty key(s) skipped — not stored in DB)")
            if args.activate:
                print(
                    f"  Merchant {args.merchant_id} will now use DB-backed TSYS "
                    "credentials (env-var fallback bypassed)."
                )
            print("=" * 62)
            print()

        except Exception as exc:
            db.rollback()
            print(f"\nERROR: {exc}\n")
            sys.exit(1)


if __name__ == "__main__":
    main()
