import json

import pytest
from django.core import mail
from django_scopes import scopes_disabled

from servala.core.models import Organization, OrganizationOrigin, User


@pytest.fixture
def exoscale_origin():
    origin, _ = OrganizationOrigin.objects.get_or_create(
        name="exoscale-marketplace",
        defaults={
            "description": "Organizations created via Exoscale marketplace onboarding"
        },
    )
    return origin


@pytest.fixture
def instance_id():
    return "test-instance-123"


@pytest.fixture
def valid_osb_payload():
    return {
        "service_id": None,
        "plan_id": None,
        "context": {
            "organization_guid": "test-org-guid-123",
            "organization_name": "Test Organization",
            "organization_display_name": "Test Organization Display",
        },
        "parameters": {
            "users": [
                {
                    "email": "test@example.com",
                    "full_name": "Test User",
                    "role": "owner",
                }
            ]
        },
    }


@pytest.mark.django_db
def test_successful_onboarding_new_organization(
    mock_odoo_success,
    osb_client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    exoscale_origin,
    instance_id,
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 201
    response_data = json.loads(response.content)
    assert response_data["message"] == "Successfully enabled service"

    org = Organization.objects.get(osb_guid="test-org-guid-123")
    assert org.name == "Test Organization Display"
    assert org.origin == exoscale_origin
    assert org.namespace.startswith("org-")

    user = User.objects.get(email="test@example.com")
    assert user.first_name == "Test"
    assert user.last_name == "User"
    with scopes_disabled():
        membership = org.memberships.get(user=user)
        assert membership.role == "owner"

    billing_entity = org.billing_entity
    assert billing_entity.name == "Test Organization Display (Exoscale)"
    assert billing_entity.odoo_company_id == 123
    assert billing_entity.odoo_invoice_id == 456

    assert org.odoo_sale_order_id == 789
    assert org.odoo_sale_order_name == "SO001"

    assert len(mail.outbox) == 2
    invitation_email = mail.outbox[0]
    assert invitation_email.subject == "Welcome to Servala - Test Organization Display"
    assert "test@example.com" in invitation_email.to

    welcome_email = mail.outbox[1]
    assert welcome_email.subject == "Get started with Redis - Test Organization Display"
    assert "redis/offering/" in welcome_email.body


@pytest.mark.django_db
def test_duplicate_organization_returns_existing(
    osb_client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    exoscale_origin,
    instance_id,
):
    Organization.objects.create(
        name="Existing Org",
        osb_guid="test-org-guid-123",
        origin=exoscale_origin,
    )

    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 200
    response_data = json.loads(response.content)
    assert response_data["message"] == "Service already enabled"
    assert Organization.objects.filter(osb_guid="test-org-guid-123").count() == 1
    assert len(mail.outbox) == 1  # Only one email was sent


@pytest.mark.django_db
def test_unauthenticated_osb_api_request_fails(
    client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    instance_id,
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id

    response = client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 403


@pytest.mark.django_db
@pytest.mark.parametrize(
    "field_to_remove,expected_error",
    [
        (
            ("context", "organization_guid"),
            "organization_guid is required but missing",
        ),
        (
            ("context", "organization_name"),
            "organization_name is required but missing",
        ),
        ("service_id", "service_id is required but missing"),
        ("plan_id", "plan_id is required but missing"),
        (("parameters", "users"), "users array is required but missing"),
    ],
)
def test_missing_required_fields_error(
    osb_client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    field_to_remove,
    expected_error,
    instance_id,
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id

    if isinstance(field_to_remove, tuple):
        if field_to_remove[0] == "context":
            del valid_osb_payload["context"][field_to_remove[1]]
        elif field_to_remove[0] == "parameters":
            del valid_osb_payload["parameters"][field_to_remove[1]]
    else:
        if field_to_remove in valid_osb_payload:
            del valid_osb_payload[field_to_remove]

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert expected_error in response_data["error"]


@pytest.mark.django_db
def test_invalid_service_id_error(osb_client, valid_osb_payload, instance_id):
    valid_osb_payload["service_id"] = 99999
    valid_osb_payload["plan_id"] = 1

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert "Unknown service_id: 99999" in response_data["error"]


@pytest.mark.django_db
def test_invalid_plan_id_error(
    osb_client, test_service, valid_osb_payload, instance_id
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = 99999

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert (
        f"Unknown plan_id: 99999 for service_id: {test_service.osb_service_id}"
        in response_data["error"]
    )


@pytest.mark.django_db
def test_empty_users_array_error(
    osb_client, test_service, test_service_offering, valid_osb_payload, instance_id
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
    valid_osb_payload["parameters"]["users"] = []

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert "users array is required but missing" in response_data["error"]


@pytest.mark.django_db
def test_multiple_users_error(
    osb_client, test_service, test_service_offering, valid_osb_payload, instance_id
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
    valid_osb_payload["parameters"]["users"] = [
        {"email": "user1@example.com", "full_name": "User One"},
        {"email": "user2@example.com", "full_name": "User Two"},
    ]

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert "users array is expected to contain a single user" in response_data["error"]


@pytest.mark.django_db
def test_empty_email_address_error(
    osb_client, test_service, test_service_offering, valid_osb_payload, instance_id
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
    valid_osb_payload["parameters"]["users"] = [
        {"email": "", "full_name": "User With No Email"},
    ]

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert "Unable to create user:" in response_data["error"]


@pytest.mark.django_db
def test_invalid_json_error(osb_client, instance_id):
    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data="invalid json{",
        content_type="application/json",
    )

    assert response.status_code == 400
    response_data = json.loads(response.content)
    assert "Invalid JSON in request body" in response_data["error"]


@pytest.mark.django_db
def test_user_creation_with_name_parsing(
    mock_odoo_success,
    osb_client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    exoscale_origin,
    instance_id,
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
    valid_osb_payload["parameters"]["users"][0]["full_name"] = "John Doe Smith"

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 201
    user = User.objects.get(email="test@example.com")
    assert user.first_name == "John"
    assert user.last_name == "Doe Smith"


@pytest.mark.django_db
def test_email_normalization(
    mock_odoo_success,
    osb_client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    exoscale_origin,
    instance_id,
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
    valid_osb_payload["parameters"]["users"][0]["email"] = "  TEST@EXAMPLE.COM  "

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 201
    user = User.objects.get(email="test@example.com")
    assert user.email == "test@example.com"


@pytest.mark.django_db
def test_odoo_integration_failure_handling(
    mock_odoo_failure,
    osb_client,
    test_service,
    test_service_offering,
    valid_osb_payload,
    exoscale_origin,
    instance_id,
):
    valid_osb_payload["service_id"] = test_service.osb_service_id
    valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(valid_osb_payload),
        content_type="application/json",
    )

    assert response.status_code == 500
    response_data = json.loads(response.content)
    assert response_data["error"] == "Internal server error"


@pytest.mark.django_db
def test_organization_creation_with_context_only(
    mock_odoo_success,
    osb_client,
    test_service,
    test_service_offering,
    exoscale_origin,
    instance_id,
):
    payload = {
        "service_id": test_service.osb_service_id,
        "plan_id": test_service_offering.osb_plan_id,
        "context": {
            "organization_guid": "fallback-org-guid",
            "organization_name": "Fallback Organization",
        },
        "parameters": {
            "users": [
                {
                    "email": "fallback@example.com",
                    "full_name": "Fallback User",
                }
            ]
        },
    }

    response = osb_client.put(
        f"/api/osb/v2/service_instances/{instance_id}",
        data=json.dumps(payload),
        content_type="application/json",
    )

    assert response.status_code == 201
    org = Organization.objects.get(osb_guid="fallback-org-guid")
    assert org is not None
