# app/modules/hosting/tests/unit/test_hosted_site_service.py
"""
Unit tests for HostedSiteService.
"""

import uuid

import pytest

from app.modules.hosting.exceptions import (
    HostedSiteNotFoundException,
    InvalidStatusTransitionException,
)
from app.modules.hosting.models import HostedSiteStatus
from app.modules.hosting.services.hosted_site_service import (
    HostedSiteService,
    _slugify,
)


@pytest.mark.unit
@pytest.mark.hosting
class TestSlugify:
    """Tests for the _slugify helper."""

    def test_basic_slug(self):
        assert _slugify("My Business") == "my-business"

    def test_special_characters_removed(self):
        assert _slugify("Café & Brasserie!") == "caf-brasserie"

    def test_multiple_spaces_collapsed(self):
        assert _slugify("  Too   Many   Spaces  ") == "too-many-spaces"

    def test_max_length(self):
        long_name = "A" * 100
        assert len(_slugify(long_name)) <= 50

    def test_empty_string(self):
        assert _slugify("") == ""


@pytest.mark.unit
@pytest.mark.hosting
class TestHostedSiteService:
    """Tests for HostedSiteService CRUD operations."""

    def setup_method(self):
        self.service = HostedSiteService()

    def test_create_site(self, db, hosting_platform, system_merchant):
        """Test creating a hosted site with auto-created store."""
        unique = uuid.uuid4().hex[:8]
        site = self.service.create(
            db,
            {
                "business_name": f"New Business {unique}",
                "merchant_id": system_merchant.id,
                "contact_email": f"test-{unique}@example.com",
            },
        )
        db.commit()

        assert site.id is not None
        assert site.business_name == f"New Business {unique}"
        assert site.status == HostedSiteStatus.DRAFT
        assert site.store_id is not None

    def test_create_site_with_all_fields(self, db, hosting_platform, system_merchant):
        """Test creating a site with all optional fields."""
        unique = uuid.uuid4().hex[:8]
        site = self.service.create(
            db,
            {
                "business_name": f"Full Business {unique}",
                "merchant_id": system_merchant.id,
                "contact_name": "Jane Doe",
                "contact_email": f"jane-{unique}@example.com",
                "contact_phone": "+352 999 888",
                "internal_notes": "VIP client",
            },
        )
        db.commit()

        assert site.contact_name == "Jane Doe"
        assert site.contact_phone == "+352 999 888"
        assert site.internal_notes == "VIP client"

    def test_get_by_id(self, db, hosted_site):
        """Test getting a hosted site by ID."""
        result = self.service.get_by_id(db, hosted_site.id)
        assert result.id == hosted_site.id
        assert result.business_name == hosted_site.business_name

    def test_get_by_id_not_found(self, db):
        """Test getting non-existent site raises exception."""
        with pytest.raises(HostedSiteNotFoundException):
            self.service.get_by_id(db, 99999)

    def test_get_all(self, db, hosted_site):
        """Test listing all hosted sites."""
        sites, total = self.service.get_all(db)
        assert total >= 1
        assert len(sites) >= 1

    def test_get_all_search(self, db, hosted_site):
        """Test searching hosted sites by business name."""
        name_part = hosted_site.business_name[:12]
        sites, total = self.service.get_all(db, search=name_part)
        assert total >= 1

    def test_get_all_filter_status(self, db, hosted_site):
        """Test filtering sites by status."""
        sites, total = self.service.get_all(db, status="draft")
        assert total >= 1
        assert all(s.status == HostedSiteStatus.DRAFT for s in sites)

    def test_get_all_pagination(self, db, hosted_site):
        """Test pagination returns correct subset."""
        sites, total = self.service.get_all(db, page=1, per_page=1)
        assert len(sites) <= 1

    def test_update(self, db, hosted_site):
        """Test updating a hosted site."""
        updated = self.service.update(
            db,
            hosted_site.id,
            {"business_name": "Updated Name", "internal_notes": "Updated notes"},
        )
        db.commit()

        assert updated.business_name == "Updated Name"
        assert updated.internal_notes == "Updated notes"

    def test_delete(self, db, hosted_site):
        """Test deleting a hosted site."""
        site_id = hosted_site.id
        self.service.delete(db, site_id)
        db.commit()

        with pytest.raises(HostedSiteNotFoundException):
            self.service.get_by_id(db, site_id)


@pytest.mark.unit
@pytest.mark.hosting
class TestHostedSiteLifecycle:
    """Tests for hosted site lifecycle transitions."""

    def setup_method(self):
        self.service = HostedSiteService()

    def test_mark_poc_ready(self, db, hosted_site):
        """Test DRAFT → POC_READY transition."""
        site = self.service.mark_poc_ready(db, hosted_site.id)
        db.commit()
        assert site.status == HostedSiteStatus.POC_READY

    def test_send_proposal(self, db, hosted_site_poc_ready):
        """Test POC_READY → PROPOSAL_SENT transition."""
        site = self.service.send_proposal(
            db, hosted_site_poc_ready.id, notes="Proposal notes"
        )
        db.commit()
        assert site.status == HostedSiteStatus.PROPOSAL_SENT
        assert site.proposal_sent_at is not None
        assert site.proposal_notes == "Proposal notes"

    def test_send_proposal_without_notes(self, db, hosted_site_poc_ready):
        """Test sending proposal without notes."""
        site = self.service.send_proposal(db, hosted_site_poc_ready.id)
        db.commit()
        assert site.status == HostedSiteStatus.PROPOSAL_SENT
        assert site.proposal_sent_at is not None

    def test_cancel_from_draft(self, db, hosted_site):
        """Test DRAFT → CANCELLED transition."""
        site = self.service.cancel(db, hosted_site.id)
        db.commit()
        assert site.status == HostedSiteStatus.CANCELLED

    def test_cancel_from_poc_ready(self, db, hosted_site_poc_ready):
        """Test POC_READY → CANCELLED transition."""
        site = self.service.cancel(db, hosted_site_poc_ready.id)
        db.commit()
        assert site.status == HostedSiteStatus.CANCELLED

    def test_cancel_from_proposal_sent(self, db, hosted_site_proposal_sent):
        """Test PROPOSAL_SENT → CANCELLED transition."""
        site = self.service.cancel(db, hosted_site_proposal_sent.id)
        db.commit()
        assert site.status == HostedSiteStatus.CANCELLED

    def test_invalid_transition_draft_to_live(self, db, hosted_site):
        """Test that DRAFT → LIVE is not allowed."""
        with pytest.raises(InvalidStatusTransitionException):
            self.service._transition(db, hosted_site.id, HostedSiteStatus.LIVE)

    def test_invalid_transition_draft_to_accepted(self, db, hosted_site):
        """Test that DRAFT → ACCEPTED is not allowed."""
        with pytest.raises(InvalidStatusTransitionException):
            self.service._transition(db, hosted_site.id, HostedSiteStatus.ACCEPTED)

    def test_invalid_transition_cancelled_to_anything(self, db, hosted_site):
        """Test that CANCELLED state has no valid transitions."""
        self.service.cancel(db, hosted_site.id)
        db.commit()

        with pytest.raises(InvalidStatusTransitionException):
            self.service._transition(db, hosted_site.id, HostedSiteStatus.DRAFT)

    def test_invalid_transition_poc_ready_to_accepted(self, db, hosted_site_poc_ready):
        """Test that POC_READY → ACCEPTED is not allowed (must go through PROPOSAL_SENT)."""
        with pytest.raises(InvalidStatusTransitionException):
            self.service._transition(
                db, hosted_site_poc_ready.id, HostedSiteStatus.ACCEPTED
            )

    def test_suspend_from_live(self, db, hosted_site_proposal_sent):
        """Test LIVE → SUSPENDED transition."""
        # Need to get to LIVE first: accepted → live
        site = self.service._transition(
            db, hosted_site_proposal_sent.id, HostedSiteStatus.ACCEPTED
        )
        db.flush()

        # Manually set to LIVE (bypass go_live which needs StoreDomainService)
        site.status = HostedSiteStatus.LIVE
        db.flush()

        site = self.service.suspend(db, site.id)
        db.commit()
        assert site.status == HostedSiteStatus.SUSPENDED

    def test_reactivate_from_suspended(self, db, hosted_site_proposal_sent):
        """Test SUSPENDED → LIVE transition."""
        site = self.service._transition(
            db, hosted_site_proposal_sent.id, HostedSiteStatus.ACCEPTED
        )
        db.flush()

        site.status = HostedSiteStatus.LIVE
        db.flush()

        site = self.service.suspend(db, site.id)
        db.flush()

        site = self.service._transition(db, site.id, HostedSiteStatus.LIVE)
        db.commit()
        assert site.status == HostedSiteStatus.LIVE


@pytest.mark.unit
@pytest.mark.hosting
class TestHostedSiteFromProspect:
    """Tests for creating hosted sites from prospects via prospect_id."""

    def setup_method(self):
        self.service = HostedSiteService()

    def test_create_with_prospect_id(self, db, hosting_platform):
        """Test creating a hosted site with prospect_id auto-creates merchant."""
        from app.modules.prospecting.models import Prospect, ProspectContact

        unique = uuid.uuid4().hex[:8]
        prospect = Prospect(
            channel="digital",
            domain_name=f"prospect-{unique}.lu",
            business_name="Prospect Business",
            status="active",
            has_website=True,
        )
        db.add(prospect)
        db.flush()

        # Add email contact (needed for merchant creation)
        db.add(ProspectContact(
            prospect_id=prospect.id,
            contact_type="email",
            value=f"hello-{unique}@test.lu",
            is_primary=True,
        ))
        db.commit()
        db.refresh(prospect)

        site = self.service.create(
            db,
            {
                "business_name": "Prospect Business",
                "prospect_id": prospect.id,
                "contact_email": f"hello-{unique}@test.lu",
            },
        )
        db.commit()

        assert site.id is not None
        assert site.prospect_id == prospect.id
        assert site.business_name == "Prospect Business"
        assert site.store.merchant is not None

    def test_create_without_merchant_or_prospect_raises(self, db, hosting_platform):
        """Test that creating without merchant_id or prospect_id raises ValueError."""
        with pytest.raises(ValueError, match="Either merchant_id or prospect_id"):
            self.service.create(
                db,
                {"business_name": "No Owner"},
            )

    def test_create_with_nonexistent_prospect_raises(self, db, hosting_platform):
        """Test creating with non-existent prospect raises exception."""
        from app.modules.prospecting.exceptions import ProspectNotFoundException

        with pytest.raises(ProspectNotFoundException):
            self.service.create(
                db,
                {"business_name": "Ghost", "prospect_id": 99999},
            )
