from datetime import UTC, datetime, timedelta

from sqlalchemy import func, select
from sqlalchemy.orm import Session, selectinload

from app.models.customer import Customer, CustomerAddress, CustomerContact
from app.schemas.customer import (
    CustomerAddressCreate,
    CustomerAddressUpdate,
    CustomerContactCreate,
    CustomerContactUpdate,
    CustomerCreate,
    CustomerUpdate,
)


def _email(value: object) -> str:
    return "" if value is None else str(value)


def _url(value: object) -> str:
    return "" if value is None else str(value)


class CustomerRepository:
    @staticmethod
    def get_all(db: Session) -> list[Customer]:
        return list(
            db.scalars(
                select(Customer)
                .options(
                    selectinload(Customer.addresses),
                    selectinload(Customer.contacts),
                )
                .order_by(Customer.created_at.desc())
            )
        )

    @staticmethod
    def get_by_id(db: Session, customer_id: int) -> Customer | None:
        return db.scalar(
            select(Customer)
            .where(Customer.id == customer_id)
            .options(
                selectinload(Customer.addresses),
                selectinload(Customer.contacts),
            )
        )

    @staticmethod
    def get_by_number(db: Session, customer_number: str) -> Customer | None:
        return db.scalar(select(Customer).where(Customer.customer_number == customer_number))

    @staticmethod
    def find_number_conflict(
        db: Session,
        *,
        customer_number: str,
        exclude_customer_id: int | None = None,
    ) -> Customer | None:
        query = select(Customer).where(Customer.customer_number == customer_number)
        if exclude_customer_id is not None:
            query = query.where(Customer.id != exclude_customer_id)
        return db.scalar(query)

    @staticmethod
    def count_all(db: Session) -> int:
        return db.scalar(select(func.count(Customer.id))) or 0

    @staticmethod
    def count_active(db: Session) -> int:
        return db.scalar(select(func.count(Customer.id)).where(Customer.status == "active")) or 0

    @staticmethod
    def count_recent(db: Session, days: int = 30) -> int:
        since = datetime.now(UTC) - timedelta(days=days)
        return db.scalar(
            select(func.count(Customer.id))
            .where(Customer.created_at >= since)
        ) or 0

    @staticmethod
    def get_latest(db: Session, limit: int = 5) -> list[Customer]:
        return list(
            db.scalars(
                select(Customer)
                .options(selectinload(Customer.addresses), selectinload(Customer.contacts))
                .order_by(Customer.created_at.desc())
                .limit(limit)
            )
        )

    @staticmethod
    def create(db: Session, customer: CustomerCreate) -> Customer:
        db_customer = Customer(
            customer_number=customer.customer_number,
            company_name=customer.company_name,
            legal_name=customer.legal_name,
            customer_type=customer.customer_type,
            status=customer.status,
            industry=customer.industry,
            website=_url(customer.website),
            email=_email(customer.email),
            phone=customer.phone,
            tax_number=customer.tax_number,
            vat_id=customer.vat_id,
            notes=customer.notes,
        )
        db_customer.addresses = [
            CustomerRepository._address_from_payload(address)
            for address in customer.addresses
        ]
        db_customer.contacts = [
            CustomerRepository._contact_from_payload(contact)
            for contact in customer.contacts
        ]
        CustomerRepository._normalize_primary(db_customer.addresses)
        CustomerRepository._normalize_primary(db_customer.contacts)

        db.add(db_customer)
        db.commit()
        db.refresh(db_customer)

        return CustomerRepository.get_by_id(db, db_customer.id) or db_customer

    @staticmethod
    def update(db: Session, db_customer: Customer, customer: CustomerUpdate) -> Customer:
        db_customer.customer_number = customer.customer_number
        db_customer.company_name = customer.company_name
        db_customer.legal_name = customer.legal_name
        db_customer.customer_type = customer.customer_type
        db_customer.status = customer.status
        db_customer.industry = customer.industry
        db_customer.website = _url(customer.website)
        db_customer.email = _email(customer.email)
        db_customer.phone = customer.phone
        db_customer.tax_number = customer.tax_number
        db_customer.vat_id = customer.vat_id
        db_customer.notes = customer.notes

        db_customer.addresses = [
            CustomerRepository._address_from_payload(address)
            for address in customer.addresses
        ]
        CustomerRepository._normalize_primary(db_customer.addresses)

        db.commit()
        db.refresh(db_customer)

        return CustomerRepository.get_by_id(db, db_customer.id) or db_customer

    @staticmethod
    def delete(db: Session, db_customer: Customer) -> None:
        db.delete(db_customer)
        db.commit()

    @staticmethod
    def get_contacts(db: Session, customer_id: int) -> list[CustomerContact]:
        return list(
            db.scalars(
                select(CustomerContact)
                .where(CustomerContact.customer_id == customer_id)
                .order_by(CustomerContact.is_primary.desc(), CustomerContact.last_name)
            )
        )

    @staticmethod
    def get_contact_by_id(
        db: Session,
        customer_id: int,
        contact_id: int,
    ) -> CustomerContact | None:
        return db.scalar(
            select(CustomerContact)
            .where(CustomerContact.customer_id == customer_id)
            .where(CustomerContact.id == contact_id)
        )

    @staticmethod
    def create_contact(
        db: Session,
        customer_id: int,
        contact: CustomerContactCreate,
    ) -> CustomerContact:
        db_contact = CustomerRepository._contact_from_payload(contact)
        db_contact.customer_id = customer_id
        db.add(db_contact)
        db.flush()

        if db_contact.is_primary:
            CustomerRepository._clear_other_primary_contacts(db, customer_id, db_contact.id)

        db.commit()
        db.refresh(db_contact)
        return db_contact

    @staticmethod
    def update_contact(
        db: Session,
        db_contact: CustomerContact,
        contact: CustomerContactUpdate,
    ) -> CustomerContact:
        db_contact.first_name = contact.first_name
        db_contact.last_name = contact.last_name
        db_contact.position = contact.position
        db_contact.email = _email(contact.email)
        db_contact.phone = contact.phone
        db_contact.mobile = contact.mobile
        db_contact.is_primary = contact.is_primary
        db_contact.notes = contact.notes

        if db_contact.is_primary:
            CustomerRepository._clear_other_primary_contacts(db, db_contact.customer_id, db_contact.id)

        db.commit()
        db.refresh(db_contact)
        return db_contact

    @staticmethod
    def delete_contact(db: Session, db_contact: CustomerContact) -> None:
        db.delete(db_contact)
        db.commit()

    @staticmethod
    def _address_from_payload(address: CustomerAddressCreate | CustomerAddressUpdate) -> CustomerAddress:
        return CustomerAddress(
            type=address.type,
            street=address.street,
            postal_code=address.postal_code,
            city=address.city,
            state=address.state,
            country=address.country,
            is_primary=address.is_primary,
        )

    @staticmethod
    def _contact_from_payload(contact: CustomerContactCreate | CustomerContactUpdate) -> CustomerContact:
        return CustomerContact(
            first_name=contact.first_name,
            last_name=contact.last_name,
            position=contact.position,
            email=_email(contact.email),
            phone=contact.phone,
            mobile=contact.mobile,
            is_primary=contact.is_primary,
            notes=contact.notes,
        )

    @staticmethod
    def _normalize_primary(items: list[CustomerAddress] | list[CustomerContact]) -> None:
        primary_seen = False
        for item in items:
            if item.is_primary and not primary_seen:
                primary_seen = True
            elif item.is_primary:
                item.is_primary = False

        if items and not primary_seen:
            items[0].is_primary = True

    @staticmethod
    def _clear_other_primary_contacts(db: Session, customer_id: int, contact_id: int) -> None:
        contacts = db.scalars(
            select(CustomerContact)
            .where(CustomerContact.customer_id == customer_id)
            .where(CustomerContact.id != contact_id)
        )
        for contact in contacts:
            contact.is_primary = False
