"""Completed-transaction tests (Trust System — Phase 1)."""

import os
import unittest
from decimal import Decimal

os.environ["TEST_DATABASE_URL"] = "sqlite:///:memory:"

from app import create_app
from app.extensions import db
from app.models import (
    Category,
    CompletedTransaction,
    Conversation,
    Listing,
    ListingCondition,
    ListingStatus,
    ListingVisibility,
    Notification,
    Offer,
    OfferStatus,
    ReviewStatus,
    User,
)
from app.marketplace.services import (
    TransactionError,
    complete_transaction,
    get_transaction_for_listing,
)
from app.negotiation.services import accept_offer, counter_offer, create_offer


class CompletedTransactionTests(unittest.TestCase):
    def setUp(self):
        self.app = create_app("testing")
        self.ctx = self.app.app_context()
        self.ctx.push()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.ctx.pop()

    # --- factories ---
    def _user(self, email):
        u = User(is_verified=True, email=email, password_hash="hashed")
        db.session.add(u)
        db.session.commit()
        return u

    def _listing(self, seller, idx=1):
        cat = db.session.scalar(db.select(Category).filter_by(name="Books"))
        if cat is None:
            cat = Category(name="Books", description="d", is_active=True, display_order=1)
            db.session.add(cat)
            db.session.commit()
        listing = Listing(
            sku=f"SKU-{seller.id}-{idx}", seller_id=seller.id, category_id=cat.id,
            title=f"Item {idx}", slug=f"item-{seller.id}-{idx}", description="d", price=Decimal("50.00"),
            condition=ListingCondition.GOOD, status=ListingStatus.AVAILABLE, visibility=ListingVisibility.PUBLIC,
            review_status=ReviewStatus.APPROVED,
        )
        db.session.add(listing)
        db.session.commit()
        return listing

    def _conversation(self, buyer, seller, listing):
        convo = Conversation(listing_id=listing.id, buyer_id=buyer.id, seller_id=seller.id)
        db.session.add(convo)
        db.session.commit()
        return convo

    def _reserved_sale(self):
        """Build a buyer/seller/listing with an accepted offer (listing RESERVED)."""
        buyer, seller = self._user("b@c.edu"), self._user("s@c.edu")
        listing = self._listing(seller)
        convo = self._conversation(buyer, seller, listing)
        offer = Offer(listing_id=listing.id, conversation_id=convo.id, buyer_id=buyer.id,
                      seller_id=seller.id, amount=Decimal("40.00"), status=OfferStatus.PENDING)
        db.session.add(offer)
        db.session.commit()
        accept_offer(offer, seller)  # → listing RESERVED, offer ACCEPTED
        return buyer, seller, listing, offer

    # --- core flow ---
    def test_transaction_created(self):
        buyer, seller, listing, offer = self._reserved_sale()
        tx = complete_transaction(listing, seller)
        self.assertIsInstance(tx, CompletedTransaction)
        self.assertEqual(tx.listing_id, listing.id)
        self.assertEqual(tx.offer_id, offer.id)
        self.assertEqual(tx.buyer_id, buyer.id)
        self.assertEqual(tx.seller_id, seller.id)
        self.assertIsNotNone(tx.completed_at)

    def test_listing_becomes_sold(self):
        _, seller, listing, _ = self._reserved_sale()
        complete_transaction(listing, seller)
        self.assertEqual(listing.status, ListingStatus.SOLD)

    def test_offer_becomes_completed(self):
        _, seller, listing, offer = self._reserved_sale()
        complete_transaction(listing, seller)
        self.assertEqual(offer.status, OfferStatus.COMPLETED)

    def test_notifications_sent_to_both_parties(self):
        buyer, seller, listing, _ = self._reserved_sale()
        complete_transaction(listing, seller)
        for user, expected in [(seller, "successfully completed"), (buyer, "marked the transaction as complete")]:
            notifs = db.session.scalars(
                db.select(Notification).where(
                    Notification.user_id == user.id, Notification.title == "Transaction Completed"
                )
            ).all()
            self.assertEqual(len(notifs), 1)
            self.assertIn(expected, notifs[0].message)

    def test_no_new_offers_after_completion(self):
        buyer, seller, listing, _ = self._reserved_sale()
        complete_transaction(listing, seller)
        with self.assertRaises(Exception):
            create_offer(listing.conversations[0], buyer, Decimal("30.00"))

    # --- guards ---
    def test_duplicate_completion_prevented(self):
        _, seller, listing, _ = self._reserved_sale()
        complete_transaction(listing, seller)
        with self.assertRaises(TransactionError):
            complete_transaction(listing, seller)
        # exactly one transaction row persisted
        rows = db.session.scalars(
            db.select(CompletedTransaction).where(CompletedTransaction.listing_id == listing.id)
        ).all()
        self.assertEqual(len(rows), 1)

    def test_unauthorized_user_blocked(self):
        _, seller, listing, _ = self._reserved_sale()
        intruder = self._user("x@c.edu")
        with self.assertRaises(TransactionError):
            complete_transaction(listing, intruder)
        self.assertIsNone(get_transaction_for_listing(listing))
        self.assertEqual(listing.status, ListingStatus.RESERVED)  # unchanged

    def test_cannot_complete_without_reservation(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller)  # AVAILABLE, no accepted offer
        with self.assertRaises(TransactionError):
            complete_transaction(listing, seller)

    # --- buyer resolution is canonical (works for seller counter-offers) ---
    def test_buyer_is_conversation_buyer_for_counter_offer(self):
        buyer, seller = self._user("b@c.edu"), self._user("s@c.edu")
        listing = self._listing(seller)
        convo = self._conversation(buyer, seller, listing)
        offer = Offer(listing_id=listing.id, conversation_id=convo.id, buyer_id=buyer.id,
                      seller_id=seller.id, amount=Decimal("40.00"), status=OfferStatus.PENDING)
        db.session.add(offer)
        db.session.commit()
        counter = counter_offer(offer, seller, Decimal("45.00"))  # seller counters
        accept_offer(counter, buyer)                              # buyer accepts → RESERVED

        tx = complete_transaction(listing, seller)
        # even though the accepted offer was made by the seller, the transaction's
        # buyer is the conversation's buyer (the person who actually bought).
        self.assertEqual(tx.buyer_id, buyer.id)
        self.assertEqual(tx.seller_id, seller.id)
        self.assertEqual(tx.offer_id, counter.id)


if __name__ == "__main__":
    unittest.main()
