# pyright: reportCallIssue=false
import asyncio
import contextlib
import uuid
from collections.abc import AsyncIterator, Coroutine
from typing import Any

import httpx
import pycountry
import pycountry.db
import structlog
from babel.numbers import format_currency
from plain_client import (
    ComponentContainerContentInput,
    ComponentContainerInput,
    ComponentCopyButtonInput,
    ComponentDividerInput,
    ComponentDividerSpacingSize,
    ComponentInput,
    ComponentLinkButtonInput,
    ComponentRowContentInput,
    ComponentRowInput,
    ComponentSpacerInput,
    ComponentSpacerSize,
    ComponentTextColor,
    ComponentTextInput,
    ComponentTextSize,
    CreateThreadInput,
    CustomerIdentifierInput,
    EmailAddressInput,
    Plain,
    ThreadsFilter,
    UpsertCustomerIdentifierInput,
    UpsertCustomerInput,
    UpsertCustomerOnCreateInput,
    UpsertCustomerOnUpdateInput,
    UpsertCustomerUpsertCustomer,
)
from sqlalchemy import func, select
from sqlalchemy.orm import contains_eager

from polar.config import settings
from polar.exceptions import PolarError
from polar.models import (
    Customer,
    Order,
    Organization,
    Product,
    User,
    UserOrganization,
)
from polar.models.organization import OrganizationStatus
from polar.models.organization_review import OrganizationReview
from polar.postgres import AsyncSession
from polar.user.repository import UserRepository

from .schemas import (
    CustomerCard,
    CustomerCardKey,
    CustomerCardsRequest,
    CustomerCardsResponse,
)

log = structlog.get_logger(__name__)


class PlainServiceError(PolarError): ...


class AccountAdminDoesNotExistError(PlainServiceError):
    def __init__(self, account_id: uuid.UUID) -> None:
        self.account_id = account_id
        super().__init__(f"Account admin does not exist for account ID {account_id}")


class AccountReviewThreadCreationError(PlainServiceError):
    def __init__(self, account_id: uuid.UUID, message: str) -> None:
        self.account_id = account_id
        self.message = message
        super().__init__(
            f"Error creating thread for account ID {account_id}: {message}"
        )


class NoUserFoundError(PlainServiceError):
    def __init__(self, organization_id: uuid.UUID) -> None:
        self.organization_id = organization_id
        super().__init__(f"No user found for organization {organization_id}")


_card_getter_semaphore = asyncio.Semaphore(3)


async def _card_getter_task(
    coroutine: Coroutine[Any, Any, CustomerCard | None],
) -> CustomerCard | None:
    async with _card_getter_semaphore:
        return await coroutine


class PlainService:
    enabled = settings.PLAIN_TOKEN is not None

    async def get_cards(
        self, session: AsyncSession, request: CustomerCardsRequest
    ) -> CustomerCardsResponse:
        tasks: list[asyncio.Task[CustomerCard | None]] = []
        async with asyncio.TaskGroup() as tg:
            if CustomerCardKey.user in request.cardKeys:
                tasks.append(
                    tg.create_task(
                        _card_getter_task(self._get_user_card(session, request))
                    )
                )
            if CustomerCardKey.organization in request.cardKeys:
                tasks.append(
                    tg.create_task(
                        _card_getter_task(self._get_organization_card(session, request))
                    )
                )
            if CustomerCardKey.customer in request.cardKeys:
                tasks.append(
                    tg.create_task(
                        _card_getter_task(self.get_customer_card(session, request))
                    )
                )
            if CustomerCardKey.order in request.cardKeys:
                tasks.append(
                    tg.create_task(
                        _card_getter_task(self._get_order_card(session, request))
                    )
                )
            if CustomerCardKey.snippets in request.cardKeys:
                tasks.append(
                    tg.create_task(
                        _card_getter_task(self._get_snippets_card(session, request))
                    )
                )

        cards = [card for task in tasks if (card := task.result()) is not None]
        return CustomerCardsResponse(cards=cards)

    async def create_organization_review_thread(
        self, session: AsyncSession, organization: Organization
    ) -> None:
        if not self.enabled:
            return

        user_repository = UserRepository.from_session(session)
        if organization.account is None:
            from polar.organization.tasks import OrganizationAccountNotSet

            raise OrganizationAccountNotSet(organization.id)
        admin = await user_repository.get_by_id(organization.account.admin_id)
        if admin is None:
            raise AccountAdminDoesNotExistError(organization.account.admin_id)

        async with self._get_plain_client() as plain:
            customer_result = await plain.upsert_customer(
                UpsertCustomerInput(
                    identifier=UpsertCustomerIdentifierInput(email_address=admin.email),
                    on_create=UpsertCustomerOnCreateInput(
                        external_id=str(admin.id),
                        full_name=admin.email,
                        email=EmailAddressInput(
                            email=admin.email, is_verified=admin.email_verified
                        ),
                    ),
                    on_update=UpsertCustomerOnUpdateInput(
                        email=EmailAddressInput(
                            email=admin.email, is_verified=admin.email_verified
                        ),
                    ),
                )
            )
            if customer_result.error is not None:
                raise AccountReviewThreadCreationError(
                    organization.account.id, customer_result.error.message
                )

            match organization.status:
                case OrganizationStatus.INITIAL_REVIEW:
                    title = "Initial Account Review"
                case OrganizationStatus.ONGOING_REVIEW:
                    title = "Ongoing Account Review"
                case _:
                    raise ValueError("Organization is not under review")

            thread_result = await plain.create_thread(
                CreateThreadInput(
                    customer_identifier=self._get_customer_identifier(
                        customer_result, admin.email
                    ),
                    title=title,
                    label_type_ids=["lt_01JFG7F4N67FN3MAWK06FJ8FPG"],
                    components=[
                        ComponentInput(
                            component_text=ComponentTextInput(
                                text=f"The organization `{organization.slug}` should be reviewed, as it hit a threshold."
                            )
                        ),
                        ComponentInput(
                            component_spacer=ComponentSpacerInput(
                                spacer_size=ComponentSpacerSize.M
                            )
                        ),
                        ComponentInput(
                            component_link_button=ComponentLinkButtonInput(
                                link_button_url=settings.generate_backoffice_url(
                                    f"/organizations-v2/{organization.id}"
                                ),
                                link_button_label="Review organization ↗",
                            )
                        ),
                    ],
                )
            )
            if thread_result.error is not None:
                raise AccountReviewThreadCreationError(
                    organization.account.id, thread_result.error.message
                )

    async def create_appeal_review_thread(
        self,
        session: AsyncSession,
        organization: Organization,
        review: OrganizationReview,
    ) -> None:
        """Create Plain ticket for organization appeal review."""
        if not self.enabled:
            return

        user_repository = UserRepository.from_session(session)
        users = await user_repository.get_all_by_organization(organization.id)
        if len(users) == 0:
            raise NoUserFoundError(organization.id)
        user = users[0]

        # Create Plain ticket for appeal review
        async with self._get_plain_client() as plain:
            customer_result = await plain.upsert_customer(
                UpsertCustomerInput(
                    identifier=UpsertCustomerIdentifierInput(email_address=user.email),
                    on_create=UpsertCustomerOnCreateInput(
                        external_id=str(user.id),
                        full_name=user.email,
                        email=EmailAddressInput(
                            email=user.email, is_verified=user.email_verified
                        ),
                    ),
                    on_update=UpsertCustomerOnUpdateInput(
                        email=EmailAddressInput(
                            email=user.email, is_verified=user.email_verified
                        ),
                    ),
                )
            )

            if customer_result.error is not None:
                raise AccountReviewThreadCreationError(
                    user.id, customer_result.error.message
                )

            # Create the thread with detailed appeal information
            thread_result = await plain.create_thread(
                CreateThreadInput(
                    customer_identifier=self._get_customer_identifier(
                        customer_result, user.email
                    ),
                    title=f"Organization Appeal - {organization.slug}",
                    label_type_ids=["lt_01K3QWYTDV7RSS7MM2RC584X41"],
                    components=[
                        ComponentInput(
                            component_text=ComponentTextInput(
                                text=f"The organization `{organization.slug}` has submitted an appeal for review after AI validation {review.verdict}."
                            )
                        ),
                        ComponentInput(
                            component_container=ComponentContainerInput(
                                container_content=[
                                    ComponentContainerContentInput(
                                        component_text=ComponentTextInput(
                                            text=organization.name or organization.slug
                                        )
                                    ),
                                    ComponentContainerContentInput(
                                        component_divider=ComponentDividerInput(
                                            divider_spacing_size=ComponentDividerSpacingSize.M
                                        )
                                    ),
                                    # Organization ID
                                    ComponentContainerContentInput(
                                        component_row=ComponentRowInput(
                                            row_main_content=[
                                                ComponentRowContentInput(
                                                    component_text=ComponentTextInput(
                                                        text="Organization ID",
                                                        text_size=ComponentTextSize.S,
                                                        text_color=ComponentTextColor.MUTED,
                                                    )
                                                ),
                                                ComponentRowContentInput(
                                                    component_text=ComponentTextInput(
                                                        text=str(organization.id)
                                                    )
                                                ),
                                            ],
                                            row_aside_content=[
                                                ComponentRowContentInput(
                                                    component_copy_button=ComponentCopyButtonInput(
                                                        copy_button_value=str(
                                                            organization.id
                                                        ),
                                                        copy_button_tooltip_label="Copy Organization ID",
                                                    )
                                                )
                                            ],
                                        )
                                    ),
                                    # Backoffice Link
                                    ComponentContainerContentInput(
                                        component_link_button=ComponentLinkButtonInput(
                                            link_button_url=settings.generate_backoffice_url(
                                                f"/organizations-v2/{organization.id}"
                                            ),
                                            link_button_label="View in Backoffice",
                                        )
                                    ),
                                ]
                            )
                        ),
                    ],
                )
            )

            if thread_result.error is not None:
                raise AccountReviewThreadCreationError(
                    user.id, thread_result.error.message
                )

    async def create_organization_deletion_thread(
        self,
        session: AsyncSession,
        organization: Organization,
        requesting_user: User,
        blocked_reasons: list[str],
    ) -> None:
        """Create Plain ticket for organization deletion request."""
        if not self.enabled:
            return

        async with self._get_plain_client() as plain:
            customer_result = await plain.upsert_customer(
                UpsertCustomerInput(
                    identifier=UpsertCustomerIdentifierInput(
                        email_address=requesting_user.email
                    ),
                    on_create=UpsertCustomerOnCreateInput(
                        external_id=str(requesting_user.id),
                        full_name=requesting_user.email,
                        email=EmailAddressInput(
                            email=requesting_user.email,
                            is_verified=requesting_user.email_verified,
                        ),
                    ),
                    on_update=UpsertCustomerOnUpdateInput(
                        email=EmailAddressInput(
                            email=requesting_user.email,
                            is_verified=requesting_user.email_verified,
                        ),
                    ),
                )
            )
            if customer_result.error is not None:
                raise AccountReviewThreadCreationError(
                    organization.id, customer_result.error.message
                )

            reasons_text = ", ".join(blocked_reasons) if blocked_reasons else "unknown"

            thread_result = await plain.create_thread(
                CreateThreadInput(
                    customer_identifier=self._get_customer_identifier(
                        customer_result, requesting_user.email
                    ),
                    title=f"Organization Deletion Request - {organization.slug}",
                    label_type_ids=["lt_01JKD9ASBPVX09YYXGHSXZRWSA"],
                    components=[
                        ComponentInput(
                            component_text=ComponentTextInput(
                                text=f"User has requested deletion of organization `{organization.slug}`."
                            )
                        ),
                        ComponentInput(
                            component_text=ComponentTextInput(
                                text=f"Blocked reasons: {reasons_text}",
                                text_color=ComponentTextColor.MUTED,
                            )
                        ),
                        ComponentInput(
                            component_spacer=ComponentSpacerInput(
                                spacer_size=ComponentSpacerSize.M
                            )
                        ),
                        ComponentInput(
                            component_container=ComponentContainerInput(
                                container_content=[
                                    ComponentContainerContentInput(
                                        component_text=ComponentTextInput(
                                            text=organization.name or organization.slug
                                        )
                                    ),
                                    ComponentContainerContentInput(
                                        component_divider=ComponentDividerInput(
                                            divider_spacing_size=ComponentDividerSpacingSize.M
                                        )
                                    ),
                                    ComponentContainerContentInput(
                                        component_row=ComponentRowInput(
                                            row_main_content=[
                                                ComponentRowContentInput(
                                                    component_text=ComponentTextInput(
                                                        text="Organization ID",
                                                        text_size=ComponentTextSize.S,
                                                        text_color=ComponentTextColor.MUTED,
                                                    )
                                                ),
                                                ComponentRowContentInput(
                                                    component_text=ComponentTextInput(
                                                        text=str(organization.id)
                                                    )
                                                ),
                                            ],
                                            row_aside_content=[
                                                ComponentRowContentInput(
                                                    component_copy_button=ComponentCopyButtonInput(
                                                        copy_button_value=str(
                                                            organization.id
                                                        ),
                                                        copy_button_tooltip_label="Copy Organization ID",
                                                    )
                                                )
                                            ],
                                        )
                                    ),
                                    ComponentContainerContentInput(
                                        component_link_button=ComponentLinkButtonInput(
                                            link_button_url=settings.generate_backoffice_url(
                                                f"/organizations-v2/{organization.id}"
                                            ),
                                            link_button_label="View in Backoffice",
                                        )
                                    ),
                                ]
                            )
                        ),
                    ],
                )
            )
            if thread_result.error is not None:
                raise AccountReviewThreadCreationError(
                    organization.id, thread_result.error.message
                )

    async def _get_user_card(
        self, session: AsyncSession, request: CustomerCardsRequest
    ) -> CustomerCard | None:
        email = request.customer.email

        user_repository = UserRepository.from_session(session)
        user = await user_repository.get_by_email(email)

        if user is None:
            return None

        components: list[ComponentInput] = [
            ComponentInput(
                component_container=ComponentContainerInput(
                    container_content=[
                        ComponentContainerContentInput(
                            component_row=ComponentRowInput(
                                row_main_content=[
                                    ComponentRowContentInput(
                                        component_text=ComponentTextInput(
                                            text=user.email
                                        )
                                    ),
                                ],
                                row_aside_content=[
                                    ComponentRowContentInput(
                                        component_link_button=ComponentLinkButtonInput(
                                            link_button_label="Backoffice ↗",
                                            link_button_url=settings.generate_backoffice_url(
                                                f"/users/{user.id}"
                                            ),
                                        )
                                    )
                                ],
                            )
                        ),
                        ComponentContainerContentInput(
                            component_divider=ComponentDividerInput(
                                divider_spacing_size=ComponentDividerSpacingSize.M
                            )
                        ),
                        ComponentContainerContentInput(
                            component_row=ComponentRowInput(
                                row_main_content=[
                                    ComponentRowContentInput(
                                        component_text=ComponentTextInput(
                                            text="ID",
                                            text_size=ComponentTextSize.S,
                                            text_color=ComponentTextColor.MUTED,
                                        )
                                    ),
                                    ComponentRowContentInput(
                                        component_text=ComponentTextInput(
                                            text=str(user.id)
                                        )
                                    ),
                                ],
                                row_aside_content=[
                                    ComponentRowContentInput(
                                        component_copy_button=ComponentCopyButtonInput(
                                            copy_button_value=str(user.id),
                                            copy_button_tooltip_label="Copy User ID",
                                        )
                                    )
                                ],
                            )
                        ),
                        ComponentContainerContentInput(
                            component_spacer=ComponentSpacerInput(
                                spacer_size=ComponentSpacerSize.M
                            )
                        ),
                        ComponentContainerContentInput(
                            component_text=ComponentTextInput(
                                text="Created At",
                                text_size=ComponentTextSize.S,
                                text_color=ComponentTextColor.MUTED,
                            )
                        ),
                        ComponentContainerContentInput(
                            component_text=ComponentTextInput(
                                text=user.created_at.date().isoformat()
                            )
                        ),
                        ComponentContainerContentInput(
                            component_row=ComponentRowInput(
                                row_main_content=[
                                    ComponentRowContentInput(
                                        component_text=ComponentTextInput(
                                            text="Identity Verification",
                                            text_size=ComponentTextSize.S,
                                            text_color=ComponentTextColor.MUTED,
                                        )
                                    ),
                                    ComponentRowContentInput(
                                        component_text=ComponentTextInput(
                                            text=user.identity_verification_status
                                        )
                                    ),
                                ],
                                row_aside_content=[
                                    ComponentRowContentInput(
                                        component_link_button=ComponentLinkButtonInput(
                                            link_button_label="Stripe ↗",
                                            link_button_url=f"https://dashboard.stripe.com/identity/verification-sessions/{user.identity_verification_id}",
                                        )
                                    )
                                ]
                                if user.identity_verification_id
                                else [],
                            )
                        ),
                    ]
                )
            )
        ]

        return CustomerCard(
            key=CustomerCardKey.user,
            timeToLiveSeconds=86400,
            components=[
                component.model_dump(by_alias=True, exclude_none=True)
                for component in components
            ],
        )

    async def _get_organization_card(
        self, session: AsyncSession, request: CustomerCardsRequest
    ) -> CustomerCard | None:
        email = request.customer.email

        statement = (
            select(Organization)
            .join(
                UserOrganization,
                Organization.id == UserOrganization.organization_id,
                isouter=True,
            )
            .join(User, User.id == UserOrganization.user_id)
            .where(func.lower(User.email) == email.lower())
        )
        result = await session.execute(statement)
        organizations = result.unique().scalars().all()

        if len(organizations) == 0:
            return None

        components: list[ComponentInput] = []
        for i, organization in enumerate(organizations):
            components.append(
                ComponentInput(
                    component_container=self._get_organization_component_container(
                        organization
                    )
                )
            )
            if i < len(organizations) - 1:
                components.append(
                    ComponentInput(
                        component_divider=ComponentDividerInput(
                            divider_spacing_size=ComponentDividerSpacingSize.M
                        )
                    )
                )

        return CustomerCard(
            key=CustomerCardKey.organization,
            timeToLiveSeconds=86400,
            components=[
                component.model_dump(by_alias=True, exclude_none=True)
                for component in components
            ],
        )

    def _get_organization_component_container(
        self, organization: Organization
    ) -> ComponentContainerInput:
        return ComponentContainerInput(
            container_content=[
                ComponentContainerContentInput(
                    component_row=ComponentRowInput(
                        row_main_content=[
                            ComponentRowContentInput(
                                component_text=ComponentTextInput(
                                    text=organization.name
                                )
                            ),
                            ComponentRowContentInput(
                                component_text=ComponentTextInput(
                                    text=organization.slug,
                                    text_color=ComponentTextColor.MUTED,
                                )
                            ),
                        ],
                        row_aside_content=[
                            ComponentRowContentInput(
                                component_link_button=ComponentLinkButtonInput(
                                    link_button_label="Backoffice ↗",
                                    link_button_url=settings.generate_backoffice_url(
                                        f"/organizations-v2/{organization.id}"
                                    ),
                                )
                            )
                        ],
                    )
                ),
                ComponentContainerContentInput(
                    component_divider=ComponentDividerInput(
                        divider_spacing_size=ComponentDividerSpacingSize.M
                    )
                ),
                ComponentContainerContentInput(
                    component_row=ComponentRowInput(
                        row_main_content=[
                            ComponentRowContentInput(
                                component_text=ComponentTextInput(
                                    text="ID",
                                    text_size=ComponentTextSize.S,
                                    text_color=ComponentTextColor.MUTED,
                                )
                            ),
                            ComponentRowContentInput(
                                component_text=ComponentTextInput(
                                    text=str(organization.id)
                                )
                            ),
                        ],
                        row_aside_content=[
                            ComponentRowContentInput(
                                component_copy_button=ComponentCopyButtonInput(
                                    copy_button_value=str(organization.id),
                                    copy_button_tooltip_label="Copy Organization ID",
                                )
                            )
                        ],
                    )
                ),
                ComponentContainerContentInput(
                    component_row=ComponentRowInput(
                        row_main_content=[
                            ComponentRowContentInput(
                                component_text=ComponentTextInput(
                                    text="Customer Portal",
                                    text_size=ComponentTextSize.S,
                                    text_color=ComponentTextColor.MUTED,
                                )
                            ),
                            ComponentRowContentInput(
                                component_text=ComponentTextInput(
                                    text=settings.generate_frontend_url(
                                        f"/{organization.slug}/portal"
                                    )
                                )
                            ),
                        ],
                        row_aside_content=[
                            ComponentRowContentInput(
                                component_copy_button=ComponentCopyButtonInput(
                                    copy_button_value=settings.generate_frontend_url(
                                        f"/{organization.slug}/portal"
                                    ),
                                    copy_button_tooltip_label="Copy URL",
                                )
                            )
