from dataclasses import field
from datetime import date
from re import match

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates

from ..base import Base
from ..custom_types import (
    SamplesheetString,
    samplesheet_str,
    samplesheet_str_pk,
    stripped_str,
    stripped_str_pk,
)
from .entities import Lab, Person


class Platform(Base, kw_only=True):
    __tablename__ = 'platform'

    # Platform attributes
    name: Mapped[stripped_str_pk]
    data_set_id_prefix: Mapped[str] = mapped_column(SamplesheetString(length=2))
    sample_id_prefix: Mapped[str] = mapped_column(SamplesheetString(length=2))
    data_set_id_length: Mapped[int]
    sample_id_length: Mapped[int]

    @validates('data_set_id_prefix', 'sample_id_prefix')
    def check_prefix(self, key: str, prefix: str) -> str:
        pattern = r'^[A-Z]{2}$'
        prefix = prefix.upper().strip()

        if match(pattern, prefix) is None:
            raise ValueError(
                f'[green]{key}[/] [orange1]{prefix}[/] does not match the pattern [green]{pattern}[/].'
            )

        return prefix

    @validates('data_set_id_length', 'sample_id_length')
    def check_id_length(self, key: str, id_length: int) -> int:
        min_id_length = 7
        max_id_length = 9

        if not min_id_length <= id_length <= max_id_length:
            raise ValueError(
                f'[green]{key}[/] must be between {min_id_length} and {max_id_length}, but {id_length} was given.'
            )

        return id_length

    @property
    def prefixes(self) -> dict[str, str]:
        return {'DataSet': self.data_set_id_prefix, 'Sample': self.sample_id_prefix}

    @property
    def id_lengths(self) -> dict[str, int]:
        return {'DataSet': self.data_set_id_length, 'Sample': self.sample_id_length}


class DataSet(Base, kw_only=True):
    __tablename__ = 'data_set'

    # DataSet attributes
    # TODO: auto-incrementing behavior
    id: Mapped[samplesheet_str_pk]
    name: Mapped[samplesheet_str] = mapped_column(index=True)
    ilab_request_id: Mapped[stripped_str] = mapped_column(
        index=True
    )  # TODO: ilab request ID validation
    date_initialized: Mapped[date] = mapped_column(repr=False)

    # Parent foreign keys
    lab_id: Mapped[int] = mapped_column(ForeignKey('lab.id'), init=False, repr=False)
    platform_name: Mapped[str] = mapped_column(ForeignKey('platform.name'), init=False)
    submitter_id: Mapped[int] = mapped_column(
        ForeignKey('person.id'), init=False, repr=False
    )

    # Parent models
    lab: Mapped[Lab] = relationship()
    platform: Mapped[Platform] = relationship()
    submitter: Mapped[Person] = relationship()

    # Automatically set attributes
    batch_id: Mapped[int] = mapped_column(init=False, default=None, repr=False)

    # Model metadata
    id_based_on: str = field(default='date_received', init=False, repr=False)

    __mapper_args__ = {
        'polymorphic_on': 'platform_name',
    }

    # TODO: implement validation
    @validates('id')
    def check_id(self, key: str, id: str) -> str:
        return id.upper().strip()

    @validates('batch_id')
    def set_batch_id(self, key: str, batch_id: None) -> int:
        # If it's decided that more things constitute a batch, this will
        # be easy to update.

        # Note that submitter email, institution, name, and ORCID have
        # been picked instead of the person ID because a person is not
        # assigned an ID until they enter the database.

        # There is a small likelihood that two people with the same name
        # and institution both have an autogenerated email and no ORCID.
        # If these two people submit on the same day, the batch_id of
        # the two datasets will be the same.
        # TODO: this should be fixed somehow
        to_hash = (
            self.date_initialized,
            self.submitter.email,
            self.submitter.institution_id,
            self.submitter.first_name,
            self.submitter.last_name,
            self.submitter.orcid,
        )
        return hash(to_hash)


class Sample(Base, kw_only=True):
    __tablename__ = 'sample'

    # Sample attributes
    id: Mapped[samplesheet_str_pk]
    name: Mapped[samplesheet_str] = mapped_column(index=True)
    date_received: Mapped[date] = mapped_column(default_factory=date.today)

    # Parent foreign keys
    data_set_id: Mapped[int] = mapped_column(
        ForeignKey('data_set.id'), init=False, repr=False
    )
    platform_name: Mapped[str] = mapped_column(ForeignKey('platform.name'), init=False)

    # Model metadata
    id_based_on: str = field(default='date_received', init=False, repr=False)

    __mapper_args__ = {'polymorphic_on': 'platform_name'}

    # TODO: implement validation
    @validates('id')
    def check_id(self, key: str, id: str) -> str:
        return id.upper().strip()
