from __future__ import annotations

import uuid
from collections.abc import Iterable

from sqlalchemy import Select, func, select
from sqlalchemy.orm import Session, joinedload

from app.core.security import verify_secret
from app.domain.enums import ActivationStatus, LicenceEventType, LicenceStatus
from app.models.entities import Activation, Customer, IntegrationIdempotencyRecord, Licence, LicenceEvent


class LicensingRepository:
    def __init__(self, session: Session):
        self.session = session

    def add_licence(self, licence: Licence) -> Licence:
        self.session.add(licence)
        return licence

    def add_customer(self, customer: Customer) -> Customer:
        self.session.add(customer)
        return customer

    def add_event(
        self,
        event_type: LicenceEventType,
        result: str,
        licence: Licence | None = None,
        activation: Activation | None = None,
        public_licence_identifier: str | None = None,
        detail: dict | None = None,
        request_correlation_id: str | None = None,
    ) -> LicenceEvent:
        event = LicenceEvent(
            licence=licence,
            activation_id=activation.id if activation else None,
            event_type=event_type,
            result=result,
            public_licence_identifier=public_licence_identifier
            or (licence.public_licence_identifier if licence else None),
            detail=detail or {},
            request_correlation_id=request_correlation_id,
        )
        self.session.add(event)
        return event

    def find_licence_by_plaintext_key(self, licence_key: str) -> Licence | None:
        suffix = "".join(ch for ch in licence_key if ch.isalnum())[-4:].upper()
        candidates: Iterable[Licence] = self.session.scalars(
            select(Licence)
            .where(Licence.licence_key_suffix == suffix)
            .with_for_update()
        )
        for licence in candidates:
            if verify_secret(licence_key, licence.licence_key_hash):
                return licence
        return None

    def get_licence_for_update(self, licence_id: uuid.UUID) -> Licence | None:
        return self.session.scalar(
            select(Licence)
            .where(Licence.id == licence_id)
            .with_for_update()
        )

    def find_customer_by_external_identity(
        self,
        source: str,
        external_customer_id: str,
    ) -> Customer | None:
        return self.session.scalar(
            select(Customer).where(
                Customer.external_source == source,
                Customer.external_customer_id == external_customer_id,
            )
        )

    def list_customer_licences(self, customer: Customer) -> list[Licence]:
        return list(
            self.session.scalars(
                select(Licence)
                .where(Licence.customer_id == customer.id)
                .order_by(Licence.created_at.desc())
            )
        )

    def find_customer_licence(self, customer: Customer, licence_id: uuid.UUID) -> Licence | None:
        return self.session.scalar(
            select(Licence)
            .where(Licence.id == licence_id, Licence.customer_id == customer.id)
            .with_for_update()
        )

    def active_activation_for_installation(
        self,
        licence: Licence,
        installation_id: str,
    ) -> Activation | None:
        return self.session.scalar(
            select(Activation)
            .where(
                Activation.licence_id == licence.id,
                Activation.installation_id == installation_id,
                Activation.status == ActivationStatus.ACTIVE,
            )
            .with_for_update()
        )

    def activation_for_installation(
        self,
        licence: Licence,
        installation_id: str,
    ) -> Activation | None:
        return self.session.scalar(
            select(Activation)
            .where(
                Activation.licence_id == licence.id,
                Activation.installation_id == installation_id,
            )
            .with_for_update()
        )

    def count_active_activations(self, licence: Licence) -> int:
        return self.session.scalar(
            select(func.count(Activation.id)).where(
                Activation.licence_id == licence.id,
                Activation.status == ActivationStatus.ACTIVE,
            )
        ) or 0

    def add_activation(self, activation: Activation) -> Activation:
        self.session.add(activation)
        return activation

    def find_activation_by_id(
        self,
        activation_id: uuid.UUID,
    ) -> Activation | None:
        return self.session.scalar(
            select(Activation)
            .options(joinedload(Activation.licence))
            .where(Activation.id == activation_id)
            .with_for_update()
        )

    def list_activations_for_licence(self, licence: Licence) -> list[Activation]:
        return list(
            self.session.scalars(
                select(Activation)
                .where(Activation.licence_id == licence.id)
                .order_by(Activation.activated_at.desc())
            )
        )

    def find_activation_for_customer(
        self,
        customer: Customer,
        activation_id: uuid.UUID,
    ) -> Activation | None:
        return self.session.scalar(
            select(Activation)
            .join(Licence)
            .where(
                Activation.id == activation_id,
                Licence.customer_id == customer.id,
            )
            .with_for_update()
        )

    def find_idempotency_record(
        self,
        source: str,
        idempotency_key: str,
    ) -> IntegrationIdempotencyRecord | None:
        return self.session.scalar(
            select(IntegrationIdempotencyRecord).where(
                IntegrationIdempotencyRecord.source == source,
                IntegrationIdempotencyRecord.idempotency_key == idempotency_key,
            )
        )

    def add_idempotency_record(self, record: IntegrationIdempotencyRecord) -> IntegrationIdempotencyRecord:
        self.session.add(record)
        return record

    def list_events(self) -> list[LicenceEvent]:
        return list(self.session.scalars(select(LicenceEvent)))
