import hashlib
import secrets
import uuid
from django.conf import settings
from django.db import models
from django.db.models import Q


class Wallet(models.Model):
    TZS = "TZS"
    USDT = "USDT"
    ASSETS = [(TZS, "Tanzanian Shilling"), (USDT, "USDT")]
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="wallets")
    asset = models.CharField(max_length=4, choices=ASSETS)
    balance_minor = models.BigIntegerField(default=0)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["user", "asset"], name="unique_user_asset_wallet"),
            models.CheckConstraint(condition=Q(balance_minor__gte=0), name="wallet_balance_nonnegative"),
        ]

    def __str__(self):
        return f"{self.user.username} - {self.asset}"


class LedgerTransaction(models.Model):
    reference = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
    kind = models.CharField(max_length=40)
    created_at = models.DateTimeField(auto_now_add=True)
    memo = models.CharField(max_length=200, blank=True)

    def __str__(self):
        return f"{self.kind} {self.reference}"


class LedgerEntry(models.Model):
    DEBIT = "debit"
    CREDIT = "credit"
    DIRECTIONS = [(DEBIT, "Debit"), (CREDIT, "Credit")]
    transaction = models.ForeignKey(LedgerTransaction, on_delete=models.PROTECT, related_name="entries")
    wallet = models.ForeignKey(Wallet, on_delete=models.PROTECT, related_name="entries")
    direction = models.CharField(max_length=6, choices=DIRECTIONS)
    amount_minor = models.PositiveBigIntegerField()

    class Meta:
        constraints = [models.CheckConstraint(condition=Q(amount_minor__gt=0), name="ledger_amount_positive")]


class Transfer(models.Model):
    transaction = models.OneToOneField(LedgerTransaction, on_delete=models.PROTECT)
    sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="sent_transfers")
    recipient = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="received_transfers")
    asset = models.CharField(max_length=4, choices=Wallet.ASSETS)
    amount_minor = models.PositiveBigIntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    idempotency_key = models.CharField(max_length=128, blank=True)


class Withdrawal(models.Model):
    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"
    REVERSED = "reversed"
    STATUSES = [
        (PENDING, "Pending"), (PROCESSING, "Processing"),
        (COMPLETED, "Completed"), (FAILED, "Failed"), (REVERSED, "Reversed"),
    ]
    BANK = "bank"
    BLOCKCHAIN = "blockchain"
    CHANNELS = [(BANK, "Bank payout"), (BLOCKCHAIN, "Blockchain wallet payout")]

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    wallet = models.ForeignKey(Wallet, on_delete=models.PROTECT)
    channel = models.CharField(max_length=12, choices=CHANNELS)
    destination = models.CharField(max_length=180)
    amount_minor = models.PositiveBigIntegerField()
    fee_minor = models.PositiveBigIntegerField(default=0)
    exchange_rate_tzs_per_usdt = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)
    fiat_amount_tzs = models.PositiveBigIntegerField(default=0)
    network = models.CharField(max_length=40, blank=True)
    blockchain_tx_hash = models.CharField(max_length=128, blank=True)
    payout_reference = models.CharField(max_length=100, blank=True)
    provider_reference = models.CharField(max_length=160, blank=True)
    failure_reason = models.CharField(max_length=250, blank=True)
    blockchain_confirmations = models.PositiveIntegerField(default=0)
    blockchain_status = models.CharField(max_length=40, blank=True)
    blockchain_submitted_at = models.DateTimeField(null=True, blank=True)
    network_fee_minor = models.PositiveBigIntegerField(default=0)
    status = models.CharField(max_length=12, choices=STATUSES, default=PENDING)
    reference = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
    idempotency_key = models.CharField(max_length=128, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    completed_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.user} {self.channel} {self.reference} ({self.status})"


class AuditLog(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL)
    action = models.CharField(max_length=80)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    details = models.CharField(max_length=500, blank=True)


class Terminal(models.Model):
    class Protocol(models.TextChoices):
        HTTPS = "HTTPS", "HTTPS"
        HTTP = "HTTP", "HTTP (local testing only)"
        TCP = "TCP", "TCP"
        OTHER = "OTHER", "Other"

    class Status(models.TextChoices):
        ACTIVE = "active", "Active"
        SUSPENDED = "suspended", "Suspended"
        OFFLINE = "offline", "Offline"

    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="terminals")
    terminal_id = models.CharField(max_length=64, unique=True, default="TERM-000001")
    protocol = models.CharField(max_length=10, choices=Protocol.choices, default=Protocol.HTTPS)
    ip_address = models.GenericIPAddressField(default="127.0.0.1")
    dns_name = models.CharField(max_length=253, default="localhost")
    machine_os = models.CharField(max_length=100, default="Windows 11")
    port = models.PositiveSmallIntegerField(default=8000)
    min_receiving_usd = models.DecimalField(max_digits=14, decimal_places=2, default="10.00")
    max_receiving_usd = models.DecimalField(max_digits=14, decimal_places=2, default="1000000.00")
    status = models.CharField(max_length=12, choices=Status.choices, default=Status.ACTIVE)
    api_key_prefix = models.CharField(max_length=12, blank=True)
    api_key_hash = models.CharField(max_length=64, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_seen_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["terminal_id"]
        constraints = [
            models.CheckConstraint(condition=Q(port__gte=1) & Q(port__lte=65535), name="terminal_port_valid"),
            models.CheckConstraint(condition=Q(min_receiving_usd__gte=0), name="terminal_min_usd_nonnegative"),
            models.CheckConstraint(condition=Q(max_receiving_usd__gte=0), name="terminal_max_usd_nonnegative"),
            models.CheckConstraint(condition=Q(max_receiving_usd__gte=models.F("min_receiving_usd")), name="terminal_max_gte_min"),
        ]

    def clean(self):
        from django.core.exceptions import ValidationError
        if self.min_receiving_usd < 0 or self.max_receiving_usd < 0:
            raise ValidationError("Receiving limits cannot be negative.")
        if self.max_receiving_usd < self.min_receiving_usd:
            raise ValidationError({"max_receiving_usd": "Maximum must be greater than or equal to minimum."})
        if not 1 <= self.port <= 65535:
            raise ValidationError({"port": "Port must be between 1 and 65535."})

    @staticmethod
    def hash_api_key(raw_key):
        return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()

    def issue_api_key(self):
        raw = "tt_" + secrets.token_urlsafe(32)
        self.api_key_prefix = raw[:10]
        self.api_key_hash = self.hash_api_key(raw)
        self.save(update_fields=["api_key_prefix", "api_key_hash", "updated_at"])
        return raw

    def authenticate_api_key(self, raw_key):
        if not raw_key or not self.api_key_hash:
            return False
        return secrets.compare_digest(self.api_key_hash, self.hash_api_key(raw_key))

    def __str__(self):
        return f"{self.terminal_id} ({self.owner})"


class PayloadTransfer(models.Model):
    PENDING = "pending"
    COMPLETED = "completed"
    CANCELLED = "cancelled"
    EXPIRED = "expired"
    STATUSES = [(PENDING, "Pending"), (COMPLETED, "Completed"), (CANCELLED, "Cancelled"), (EXPIRED, "Expired")]

    reference = models.CharField(max_length=64, unique=True, editable=False)
    sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="created_payload_transfers")
    recipient = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="received_payload_transfers")
    asset = models.CharField(max_length=4, choices=Wallet.ASSETS)
    amount_minor = models.PositiveBigIntegerField()
    memo = models.CharField(max_length=200, blank=True)
    status = models.CharField(max_length=12, choices=STATUSES, default=PENDING)
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()
    completed_at = models.DateTimeField(null=True, blank=True)
    ledger_transaction = models.OneToOneField(LedgerTransaction, null=True, blank=True, on_delete=models.PROTECT, related_name="payload_transfer")

    class Meta:
        ordering = ["-created_at"]
        constraints = [models.CheckConstraint(condition=Q(amount_minor__gt=0), name="payload_transfer_amount_positive")]

    def __str__(self):
        return f"{self.reference} ({self.status})"


class IdempotencyKey(models.Model):
    key = models.CharField(max_length=128)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    request_hash = models.CharField(max_length=64)
    transaction = models.ForeignKey(LedgerTransaction, null=True, blank=True, on_delete=models.PROTECT)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [models.UniqueConstraint(fields=["user", "key"], name="unique_user_idempotency_key")]


class ProviderEvent(models.Model):
    """Stores external provider webhooks/events for deduplication and audit."""
    provider = models.CharField(max_length=40)
    event_id = models.CharField(max_length=160)
    event_type = models.CharField(max_length=80, blank=True)
    payload_hash = models.CharField(max_length=64)
    received_at = models.DateTimeField(auto_now_add=True)
    processed_at = models.DateTimeField(null=True, blank=True)
    processing_error = models.CharField(max_length=500, blank=True)

    class Meta:
        constraints = [models.UniqueConstraint(fields=["provider", "event_id"], name="unique_provider_event")]
