import hmac
import hashlib
import os
import time
import uuid

from sqlalchemy import Column, BigInteger, Boolean, Unicode
from sqlalchemy.orm import relationship

from .meta import (
    Base,
    RBase,
    UUIDType,
    now_timestamp,
    foreign_key,
)


def _generate_public_key():
    return "mps_pub_" + os.urandom(16).hex()


def _generate_secret_key():
    return "mps_sec_" + os.urandom(32).hex()


class MpsApiKey(RBase, Base):
    """HMAC key pair for REST API access scoped to a shop.

    The public_key identifies the pair and is safe to log.
    The secret_key is used to sign requests and is shown once on creation —
    it is stored plaintext because HMAC verification requires the original value.
    """

    __tablename__ = "mps_api_key"

    id = Column(UUIDType, primary_key=True, index=True)
    shop_id = Column(UUIDType, foreign_key("Shop", "id"), nullable=False, index=True)
    public_key = Column(Unicode(72), unique=True, nullable=False, index=True)
    secret_key = Column(Unicode(136), nullable=False)
    label = Column(Unicode(128), nullable=True)
    created_timestamp = Column(BigInteger, nullable=False, default=now_timestamp)
    last_used_timestamp = Column(BigInteger, nullable=True)
    is_active = Column(Boolean, nullable=False, server_default="1")

    shop = relationship("Shop", back_populates="api_keys")

    @classmethod
    def generate(cls, shop, label=None):
        """Create a new key pair for a shop. Returns (api_key, secret_key_plaintext).

        The secret_key_plaintext is the raw secret — caller must display it
        once and never retrieve it from the DB again.
        """
        public_key = _generate_public_key()
        secret_key = _generate_secret_key()
        key = cls()
        key.id = uuid.uuid1()
        key.shop = shop
        key.public_key = public_key
        key.secret_key = secret_key
        key.label = label or ""
        key.created_timestamp = now_timestamp()
        key.is_active = True
        return key, secret_key

    def verify_signature(self, method, path, timestamp_str, body_bytes, signature):
        """Verify an HMAC-SHA256 request signature.

        string_to_sign = "{METHOD}\\n{PATH}\\n{TIMESTAMP}\\n{SHA256_OF_BODY_HEX}"
        signature      = "sha256=" + hmac_sha256(secret_key, string_to_sign).hexdigest()

        Returns True if valid, False otherwise (constant-time comparison).
        """
        try:
            ts = int(timestamp_str)
        except (ValueError, TypeError):
            return False

        # Replay window: ±300 seconds
        if abs(time.time() - ts) > 300:
            return False

        body_hash = hashlib.sha256(body_bytes).hexdigest()
        string_to_sign = f"{method}\n{path}\n{timestamp_str}\n{body_hash}"
        expected = (
            "sha256="
            + hmac.new(
                self.secret_key.encode("utf-8"),
                string_to_sign.encode("utf-8"),
                hashlib.sha256,
            ).hexdigest()
        )
        return hmac.compare_digest(expected, signature)

    @property
    def masked_secret(self):
        """Last 4 chars of secret for display. Never reveal the full value."""
        return "mps_sec_..." + self.secret_key[-4:]


def get_api_key_by_public_key(dbsession, public_key):
    return (
        dbsession.query(MpsApiKey)
        .filter_by(public_key=public_key, is_active=True)
        .first()
    )
