from decimal import Decimal

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import AppError
from app.core.logging import get_logger
from app.models import Payment, PaymentMethod, PaymentStatus
from app.repositories import PaymentRepository
from app.services.log_service import LogService, LogType
from app.services.payment_service import PaymentService
from app.services.settings_service import SettingsService
from app.services.tron_service import TronService
from app.utils.helpers import crypto

logger = get_logger("app.payment_monitor")

LOOKBACK_MINUTES = 180


class PaymentMonitor:
    """Matches incoming TRON transfers to invoices by their unique payable amount."""

    def __init__(self, session: AsyncSession, tron: TronService | None = None) -> None:
        self.session = session
        self.settings = SettingsService(session)
        self.payments = PaymentRepository(session)
        self.service = PaymentService(session)
        self.logs = LogService(session)
        self.tron = tron or TronService()

    async def check_tron_payments(self) -> int:
        wallet_address = await self.settings.get_str("tron_wallet_address")
        if not wallet_address:
            return 0

        active = await self.payments.list_active_tron()
        if not active:
            return 0

        min_timestamp = int(
            min(payment.created_at.timestamp() for payment in active) * 1000
        ) - LOOKBACK_MINUTES * 60 * 1000

        transfers = await self.tron.incoming_transfers(wallet_address, max(min_timestamp, 0))
        matched = 0

        for transfer in transfers:
            if not transfer.confirmed or transfer.amount <= 0:
                continue
            if await self.payments.get_by_tx_hash(transfer.tx_hash):
                continue

            payment = await self.payments.find_active_tron_by_amount(crypto(transfer.amount))
            if not payment:
                logger.info(
                    "unmatched tron transfer %s amount=%s", transfer.tx_hash, transfer.amount
                )
                continue

            try:
                await self.service.confirm_tron_payment(payment, transfer.tx_hash, transfer.amount)
                matched += 1
            except AppError as exc:
                logger.error("failed to confirm tron payment %s: %s", transfer.tx_hash, exc.message)
                await self.logs.write(
                    LogType.PAYMENT,
                    "tron_match_failed",
                    exc.message,
                    payment_id=payment.id,
                    data={"tx_hash": transfer.tx_hash, "amount": str(transfer.amount)},
                )

        return matched

    async def verify_single(self, payment: Payment) -> bool:
        """Manual 'check payment' button: verify one invoice on demand."""
        if payment.status == PaymentStatus.PAID:
            return True
        if payment.method != PaymentMethod.TRON or payment.payable_amount is None:
            return False

        wallet_address = payment.wallet_address or await self.settings.get_str("tron_wallet_address")
        since = int(payment.created_at.timestamp() * 1000) - 60_000
        transfers = await self.tron.incoming_transfers(wallet_address, max(since, 0))

        target = crypto(Decimal(payment.payable_amount))
        for transfer in transfers:
            if not transfer.confirmed or crypto(transfer.amount) != target:
                continue
            if await self.payments.get_by_tx_hash(transfer.tx_hash):
                continue
            await self.service.confirm_tron_payment(payment, transfer.tx_hash, transfer.amount)
            return True
        return False
