"""Import-mode helpers: let admins replay legacy history through the API.

A migration script importing the classic ASP shopdb
needs two things the normal API withholds:

  1. Preserve each row's original createddate/modifieddate instead of stamping
     "now" on insert/update.
  2. Backdate event history such as USB checkouts to when they really happened.

Both are gated. The caller must BOTH be an admin AND send the request header
`X-Import-Mode: true`. Outside import mode every helper here is a no-op, so
wiring a call into a normal create/update path never changes behavior for
regular users. See docs/IMPORT-API.md for the operator manual.
"""

from datetime import datetime, timezone

from flask import request
from flask_jwt_extended import verify_jwt_in_request, current_user, get_jwt


# Request header a migration client sets to opt a request into import mode.
IMPORT_MODE_HEADER = 'X-Import-Mode'

# strptime formats accepted for import timestamps, tried in order. Covers the
# ISO 'T' form and the legacy MySQL 'space' form, with and without fractions,
# plus a bare date.
_IMPORT_DATETIME_FORMATS = (
    '%Y-%m-%dT%H:%M:%S.%f',
    '%Y-%m-%dT%H:%M:%S',
    '%Y-%m-%dT%H:%M',
    '%Y-%m-%d %H:%M:%S.%f',
    '%Y-%m-%d %H:%M:%S',
    '%Y-%m-%d %H:%M',
    '%Y-%m-%d',
)


def import_mode_active():
    """True when the request is an admin-authenticated import request.

    Needs header `X-Import-Mode: true` AND an admin caller. Safe to call from
    any request context: verify_jwt_in_request is optional here so a missing or
    bad token yields False instead of raising, and it is idempotent when the
    route already ran @jwt_required."""
    header = (request.headers.get(IMPORT_MODE_HEADER) or '').strip().lower()
    if header != 'true':
        return False
    verify_jwt_in_request(optional=True)
    user = current_user
    if user is None or not user.hasrole('admin'):
        return False
    # A scoped PAT never gets import mode: import mode is an admin-role
    # capability and a scoped token suspends the admin bypass. get_jwt is safe
    # here - an admin user means a valid JWT was decoded above.
    if get_jwt().get('patscopes') is not None:
        return False
    return True


def parse_import_datetime(value):
    """Parse an import timestamp into naive UTC, or None.

    Accepts ISO '2020-01-05T12:00:00', legacy 'YYYY-MM-DD HH:MM:SS', a bare
    date, or an already-parsed datetime. A trailing 'Z' is treated as UTC.
    Timezone-aware input is converted to UTC then stripped to naive, matching
    the repo convention of storing naive-UTC in DB DateTime columns."""
    if value is None:
        return None
    if isinstance(value, datetime):
        parsed = value
    else:
        text = str(value).strip()
        if not text:
            return None
        if text[-1] in ('Z', 'z'):
            text = text[:-1]
        parsed = None
        for fmt in _IMPORT_DATETIME_FORMATS:
            try:
                parsed = datetime.strptime(text, fmt)
                break
            except ValueError:
                continue
        if parsed is None:
            # last resort: let fromisoformat try (handles offsets like +00:00)
            try:
                parsed = datetime.fromisoformat(text)
            except ValueError:
                return None
    if parsed.tzinfo is not None:
        parsed = parsed.astimezone(timezone.utc).replace(tzinfo=None)
    return parsed


def apply_import_timestamps(instance, data):
    """Stamp createddate/modifieddate on a row from the payload, in import mode.

    Call this in a create/update path after building the instance and before
    commit. It is a no-op unless ALL hold: import mode is active (admin +
    header), the payload carries the field, and the model actually has the
    column. Explicitly setting modifieddate also suppresses the column's
    onupdate=now default on updates, so legacy history survives edits too."""
    if not data or not import_mode_active():
        return
    created = parse_import_datetime(data.get('createddate'))
    if created is not None and hasattr(instance, 'createddate'):
        instance.createddate = created
    modified = parse_import_datetime(data.get('modifieddate'))
    if modified is not None and hasattr(instance, 'modifieddate'):
        instance.modifieddate = modified
