"""
camcops_server/cc_modules/cc_idnumdef.py

===============================================================================

    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CamCOPS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

===============================================================================

**ID number definitions.**

"""

import logging
from typing import List, Optional, Tuple, TYPE_CHECKING

from cardinal_pythonlib.logs import BraceStyleAdapter
from cardinal_pythonlib.nhs import is_valid_nhs_number
from cardinal_pythonlib.reprfunc import simple_repr
from sqlalchemy.orm import Mapped, mapped_column, Session as SqlASession
from sqlalchemy.sql.sqltypes import String

from camcops_server.cc_modules.cc_pyramid import Routes
from camcops_server.cc_modules.cc_sqla_coltypes import (
    HL7AssigningAuthorityType,
    HL7IdTypeType,
    IdDescriptorColType,
    UrlColType,
)
from camcops_server.cc_modules.cc_sqlalchemy import Base

if TYPE_CHECKING:
    from camcops_server.cc_modules.cc_request import CamcopsRequest

log = BraceStyleAdapter(logging.getLogger(__name__))


# =============================================================================
# ID number validation
# =============================================================================

ID_NUM_VALIDATION_METHOD_MAX_LEN = 50


class IdNumValidationMethod(object):
    """
    Constants representing ways that CamCOPS knows to validate ID numbers.
    """

    NONE = ""  # special
    UK_NHS_NUMBER = "uk_nhs_number"


ID_NUM_VALIDATION_METHOD_CHOICES = (
    # for HTML forms: value, description
    (IdNumValidationMethod.NONE, "None"),
    (IdNumValidationMethod.UK_NHS_NUMBER, "UK NHS number"),
)


def validate_id_number(
    req: "CamcopsRequest", idnum: Optional[int], method: str
) -> Tuple[bool, str]:
    """
    Validates an ID number according to a method (as per
    :class:`IdNumValidationMethod`).

    If the number is ``None``, that's valid (that's an ID policy failure, not
    a number validation failure). If ``method`` is falsy, that's also valid
    (no constraints).

    Args:
        req: a :class:`camcops_server.cc_modules.cc_request.CamcopsRequest`
        idnum: the ID number, or ``None``
        method:

    Returns:
        tuple: ``valid, why_invalid`` where ``valid`` is ``bool`` and
        ``why_invalid`` is ``str``.

    """
    _ = req.gettext
    if idnum is None or not method:
        return True, ""
    if not isinstance(idnum, int):
        return False, _("Not an integer")
    if method == IdNumValidationMethod.UK_NHS_NUMBER:
        if is_valid_nhs_number(idnum):
            return True, ""
        else:
            return False, _("Invalid UK NHS number")
    return False, _("Unknown validation method")


# =============================================================================
# IdNumDefinition
# =============================================================================


class IdNumDefinition(Base):
    """
    Represents an ID number definition.
    """

    __tablename__ = "_idnum_definitions"

    which_idnum: Mapped[int] = mapped_column(
        primary_key=True,
        index=True,
        comment="Which of the server's ID numbers is this?",
    )
    description: Mapped[Optional[str]] = mapped_column(
        IdDescriptorColType,
        comment="Full description of the ID number",
    )
    short_description: Mapped[Optional[str]] = mapped_column(
        IdDescriptorColType,
        comment="Short description of the ID number",
    )
    hl7_id_type: Mapped[Optional[str]] = mapped_column(
        HL7IdTypeType,
        comment="HL7: Identifier Type code: 'a code corresponding to the type "
        "of identifier. In some cases, this code may be used as a "
        'qualifier to the "Assigning Authority" component.\'',
    )
    hl7_assigning_authority: Mapped[Optional[str]] = mapped_column(
        HL7AssigningAuthorityType,
        comment="HL7: Assigning Authority for ID number (unique name of the "
        "system/organization/agency/department that creates the data).",
    )
    validation_method: Mapped[Optional[str]] = mapped_column(
        String(length=ID_NUM_VALIDATION_METHOD_MAX_LEN),
        comment="Optional validation method",
    )
    fhir_id_system: Mapped[Optional[str]] = mapped_column(
        "fhir_id_system", UrlColType, comment="FHIR external ID 'system' URL"
    )

    def __init__(
        self,
        which_idnum: int = None,
        description: str = "",
        short_description: str = "",
        hl7_id_type: str = "",
        hl7_assigning_authority: str = "",
        validation_method: str = "",
        fhir_id_system: str = "",
    ):
        # We permit a "blank" constructor for automatic copying, e.g. merge_db.
        self.which_idnum = which_idnum
        self.description = description
        self.short_description = short_description
        self.hl7_id_type = hl7_id_type
        self.hl7_assigning_authority = hl7_assigning_authority
        self.validation_method = validation_method
        self.fhir_id_system = fhir_id_system

    def __repr__(self) -> str:
        return simple_repr(
            self,
            ["which_idnum", "description", "short_description"],
            with_addr=False,
        )

    def _camcops_default_fhir_id_system(self, req: "CamcopsRequest") -> str:
        """
        The built-in FHIR ID system URL that we'll use if the user hasn't
        specified one for the selected ID number type.
        """
        return req.route_url(
            Routes.FHIR_PATIENT_ID_SYSTEM, which_idnum=self.which_idnum
        )  # path will be e.g. /fhir_patient_id_system/3

    def effective_fhir_id_system(self, req: "CamcopsRequest") -> str:
        """
        If the user has set a FHIR ID system, return that. Otherwise, return
        a CamCOPS default.
        """
        return self.fhir_id_system or self._camcops_default_fhir_id_system(req)

    def verbose_fhir_id_system(self, req: "CamcopsRequest") -> str:
        """
        Returns a human-readable description of the FHIR ID system in effect,
        in HTML form.
        """
        _ = req.gettext
        if self.fhir_id_system:
            prefix = ""
            url = self.fhir_id_system
        else:
            prefix = _("Default:") + " "
            url = self._camcops_default_fhir_id_system(req)
        return f'{prefix} <a href="{url}">{url}</a>'


# =============================================================================
# Retrieving all IdNumDefinition objects
# =============================================================================


def get_idnum_definitions(dbsession: SqlASession) -> List[IdNumDefinition]:
    """
    Get all ID number definitions from the database, in order.
    """
    return list(
        dbsession.query(IdNumDefinition).order_by(IdNumDefinition.which_idnum)
    )
