from __future__ import annotations

from typing import Any, Dict, Optional, Tuple

from sqlalchemy import or_
from sqlalchemy.orm import Session

from .dto import DocumentTypeDigitizationCreate, DocumentTypeDigitizationUpdate
from .models import DocumentTypeDigitization


class DocumentTypeDigitizationService:
	@staticmethod
	def _normalize_code(code: str) -> str:
		return (code or '').strip().upper()

	@staticmethod
	def _normalize_description(description: str) -> str:
		return (description or '').strip()

	@staticmethod
	def _is_truthy_filter(value: Any, default: bool = False) -> bool:
		if value is None:
			return default
		if isinstance(value, bool):
			return value
		return str(value).strip().lower() in {'1', 'true', 'yes', 'si'}

	@staticmethod
	def get_all(
		db: Session,
		tenant_id: int,
		company_id: Optional[int],
		skip: int = 0,
		limit: int = 100,
		filters: Optional[Dict[str, Any]] = None,
		sort_by: Optional[str] = None,
		sort_order: str = 'asc',
	) -> Tuple[list[DocumentTypeDigitization], int]:
		query = db.query(DocumentTypeDigitization).filter(
			DocumentTypeDigitization.tenant_id == tenant_id,
		)

		if company_id is not None:
			query = query.filter(DocumentTypeDigitization.company_id == company_id)

		filters = filters or {}
		active_only = DocumentTypeDigitizationService._is_truthy_filter(
			filters.get('active_only'),
			default=False,
		)
		search = (filters.get('search') or '').strip()

		if active_only:
			query = query.filter(DocumentTypeDigitization.active.is_(True))

		if search:
			like = f'%{search}%'
			query = query.filter(
				or_(
					DocumentTypeDigitization.code.ilike(like),
					DocumentTypeDigitization.description.ilike(like),
				)
			)

		total = query.count()

		sort_column = {
			'id': DocumentTypeDigitization.id,
			'code': DocumentTypeDigitization.code,
			'description': DocumentTypeDigitization.description,
			'active': DocumentTypeDigitization.active,
		}.get(sort_by or 'code', DocumentTypeDigitization.code)

		if sort_order == 'desc':
			query = query.order_by(sort_column.desc())
		else:
			query = query.order_by(sort_column.asc())

		items = query.offset(skip).limit(limit).all()
		return items, total

	@staticmethod
	def get_by_id(
		db: Session,
		id: int,
		tenant_id: int,
		company_id: int,
	) -> Optional[DocumentTypeDigitization]:
		return (
			db.query(DocumentTypeDigitization)
			.filter(
				DocumentTypeDigitization.id == id,
				DocumentTypeDigitization.tenant_id == tenant_id,
				DocumentTypeDigitization.company_id == company_id,
			)
			.first()
		)

	@staticmethod
	def get_by_code(
		db: Session,
		code: str,
		tenant_id: int,
		company_id: int,
	) -> Optional[DocumentTypeDigitization]:
		normalized_code = DocumentTypeDigitizationService._normalize_code(code)
		return (
			db.query(DocumentTypeDigitization)
			.filter(
				DocumentTypeDigitization.code == normalized_code,
				DocumentTypeDigitization.tenant_id == tenant_id,
				DocumentTypeDigitization.company_id == company_id,
			)
			.first()
		)

	@staticmethod
	def create(
		db: Session,
		data: DocumentTypeDigitizationCreate,
		tenant_id: int,
		company_id: int,
	) -> DocumentTypeDigitization:
		payload = data.model_dump()
		payload['code'] = DocumentTypeDigitizationService._normalize_code(payload['code'])
		payload['description'] = DocumentTypeDigitizationService._normalize_description(
			payload['description']
		)

		if not payload['code']:
			raise ValueError('El codigo es obligatorio')
		if not payload['description']:
			raise ValueError('La descripcion es obligatoria')

		existing = DocumentTypeDigitizationService.get_by_code(
			db, payload['code'], tenant_id, company_id
		)
		if existing:
			raise ValueError(
				f'Ya existe un tipo de documento con el codigo {payload["code"]}'
			)

		db_obj = DocumentTypeDigitization(
			**payload,
			tenant_id=tenant_id,
			company_id=company_id,
		)
		db.add(db_obj)
		db.commit()
		db.refresh(db_obj)
		return db_obj

	@staticmethod
	def update(
		db: Session,
		id: int,
		tenant_id: int,
		data: DocumentTypeDigitizationUpdate,
		company_id: int,
	) -> Optional[DocumentTypeDigitization]:
		db_obj = DocumentTypeDigitizationService.get_by_id(db, id, tenant_id, company_id)
		if not db_obj:
			return None

		update_dict = data.model_dump(exclude_unset=True)

		if 'code' in update_dict:
			update_dict['code'] = DocumentTypeDigitizationService._normalize_code(update_dict['code'])
			if not update_dict['code']:
				raise ValueError('El codigo es obligatorio')
			if update_dict['code'] != db_obj.code:
				existing = DocumentTypeDigitizationService.get_by_code(
					db,
					update_dict['code'],
					tenant_id,
					company_id,
				)
				if existing:
					raise ValueError(
						f'Ya existe un tipo de documento con el codigo {update_dict["code"]}'
					)

		if 'description' in update_dict:
			update_dict['description'] = DocumentTypeDigitizationService._normalize_description(
				update_dict['description']
			)
			if not update_dict['description']:
				raise ValueError('La descripcion es obligatoria')

		for key, value in update_dict.items():
			setattr(db_obj, key, value)

		db.commit()
		db.refresh(db_obj)
		return db_obj

	@staticmethod
	def delete(
		db: Session,
		id: int,
		tenant_id: int,
		company_id: int,
	) -> bool:
		db_obj = DocumentTypeDigitizationService.get_by_id(db, id, tenant_id, company_id)
		if not db_obj:
			return False

		db_obj.active = False
		db.commit()
		return True