from collections.abc import Mapping
from enum import StrEnum
from typing import TYPE_CHECKING
from uuid import UUID

from flask import current_app
from sentry_sdk import metrics

from app.common.expressions import EvaluatableExpression

if TYPE_CHECKING:
    from app.common.data.models import Collection, Grant, GrantRecipient, Submission


class MetricAttributeName(StrEnum):
    EVENT = "event"
    COUNT = "count"
    GRANT_RECIPIENT = "grant-recipient"
    GRANT_RECIPIENT_ID = "grant-recipient-id"
    GRANT_RECIPIENT_MODE = "grant-recipient-mode"
    GRANT = "grant"
    GRANT_ID = "grant-id"
    COLLECTION = "collection"
    COLLECTION_ID = "collection-id"
    COLLECTION_TYPE = "collection-type"
    SUBMISSION = "submission"
    SUBMISSION_ID = "submission-id"
    SUBMISSION_MODE = "submission-mode"
    USER_ID = "user-id"

    FROM_STATUS = "from-status"
    TO_STATUS = "to-status"

    MANAGED_EXPRESSION_NAME = "managed-expression-name"

    FILE_FORMAT = "file-format"

    CALCULATION_INVALID_REASON = "calculation-invalid-reason"
    CALCULATION_INVALID_FIELD = "calculation-invalid-field"


class MetricEventName(StrEnum):
    SECTION_MARKED_COMPLETE = "section-marked-as-complete"
    SECTION_MARKED_INCOMPLETE = "section-marked-as-incomplete"
    SECTION_RESET_TO_IN_PROGRESS = "section-reset-to-in-progress"

    SUBMISSION_CREATED = "submission-created"
    SUBMISSION_SENT_FOR_CERTIFICATION = "submission-sent-for-certification"
    SUBMISSION_CERTIFIED = "submission-certified"
    SUBMISSION_CERTIFICATION_DECLINED = "submission-certification-declined"
    SUBMISSION_SUBMITTED = "submission-submitted"
    SUBMISSION_BLOCKED_BY_INVALID_ANSWERS = "submission-blocked-by-invalid-answers"
    SUBMISSION_REOPENED = "submission-reopened"
    SUBMISSION_CHANGES_REQUESTED = "submission-changes-requested"
    SUBMISSION_ASSESSOR_MARKED_AS_APPROVED = "submission-assessor-marked-as-approved"
    SUBMISSION_ASSESSOR_MARKED_AS_REJECTED = "submission-assessor-marked-as-rejected"

    SUBMISSION_MANAGED_VALIDATION_ERROR = "submission-managed-validation-error"
    SUBMISSION_CUSTOM_VALIDATION_ERROR = "submission-custom-validation-error"
    SUBMISSION_MANAGED_VALIDATION_SUCCESS = "submission-managed-validation-success"
    SUBMISSION_CUSTOM_VALIDATION_SUCCESS = "submission-custom-validation-success"

    COLLECTION_BLOCKED_BY_MISSING_DATA = "collection-blocked-by-missing-data"

    SECTION_RESET_TO_IN_PROGRESS_BY_CERTIFIER = "section-reset-to-in-progress-by-certifier"

    GRANT_STATUS_CHANGED = "grant-status-changed"
    COLLECTION_STATUS_CHANGED = "collection-status-changed"
    COLLECTION_COPIED = "collection-copied"

    SUBMISSIONS_EXPORTED = "submissions-exported"
    SUBMISSION_PDF_DOWNLOADED = "submission-pdf-downloaded"

    VALIDATION_CREATED_CUSTOM = "validation-created-custom"
    VALIDATION_CREATED_MANAGED = "validation-created-managed"

    CALCULATION_FIELD_INVALID = "calculation-field-invalid"


def _get_event_attributes(
    grant_recipient: GrantRecipient | None = None,
    submission: Submission | None = None,
    collection: Collection | None = None,
    grant: Grant | None = None,
    evaluatable_expression: EvaluatableExpression | None = None,
    custom_attributes: Mapping[MetricAttributeName, str | int | UUID] | None = None,
) -> dict[str, str | int | UUID]:
    attributes: dict[str, str | int | UUID] = (
        {str(k): v for k, v in custom_attributes.items()} if custom_attributes else {}
    )

    if submission:
        attributes[str(MetricAttributeName.SUBMISSION_ID)] = submission.id
        attributes[str(MetricAttributeName.SUBMISSION_MODE)] = str(submission.mode)

        if not collection:
            collection = submission.collection

        if not grant_recipient:
            grant_recipient = submission.grant_recipient

    if collection:
        attributes[str(MetricAttributeName.COLLECTION)] = collection.name
        attributes[str(MetricAttributeName.COLLECTION_ID)] = str(collection.id)
        attributes[str(MetricAttributeName.COLLECTION_TYPE)] = str(collection.type)

        if not grant:
            grant = collection.grant

    if grant_recipient:
        attributes[str(MetricAttributeName.GRANT_RECIPIENT)] = grant_recipient.organisation.name
        attributes[str(MetricAttributeName.GRANT_RECIPIENT_ID)] = str(grant_recipient.id)
        attributes[str(MetricAttributeName.GRANT_RECIPIENT_MODE)] = str(grant_recipient.mode)

        if not grant:
            grant = grant_recipient.grant

    if grant:
        attributes[str(MetricAttributeName.GRANT)] = grant.name
        attributes[str(MetricAttributeName.GRANT_ID)] = str(grant.id)

    if evaluatable_expression:
        attributes[str(MetricAttributeName.MANAGED_EXPRESSION_NAME)] = evaluatable_expression.name

    return attributes


def emit_metric_count(
    event: MetricEventName,
    count: int = 1,
    grant_recipient: GrantRecipient | None = None,
    submission: Submission | None = None,
    collection: Collection | None = None,
    grant: Grant | None = None,
    evaluatable_expression: EvaluatableExpression | None = None,
    custom_attributes: Mapping[MetricAttributeName, str | int | UUID] | None = None,
) -> None:
    attributes = _get_event_attributes(
        grant_recipient=grant_recipient,
        submission=submission,
        collection=collection,
        grant=grant,
        evaluatable_expression=evaluatable_expression,
        custom_attributes=custom_attributes,
    )

    metrics.count(event, count, attributes=attributes)

    # Just add to attributes for logging to CloudWatch, not Sentry
    log_attributes = {**attributes, str(MetricAttributeName.EVENT): event, str(MetricAttributeName.COUNT): count}

    current_app.logger.info(
        "Track metric %(event)s for count=%(count)s", dict(event=event, count=count), extra=log_attributes
    )
