"""Servicio de invitaciones de usuarios."""

import hashlib
import logging
import secrets
import ssl
from datetime import datetime, timedelta, timezone
from typing import Optional

import aiosmtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from fastapi import HTTPException
from sqlalchemy.orm import Session

from core.config import settings
from .dto import CreateInviteDTO, InviteResponseDTO, InviteValidationResult
from .models import InviteToken

logger = logging.getLogger(__name__)

INVITE_TTL_HOURS = 48


def _hash_token(token_plain: str) -> str:
    return hashlib.sha256(token_plain.encode()).hexdigest()


def _extract_token_from_url(url: str) -> Optional[str]:
    """Extract invite_token query param from a URL string."""
    from urllib.parse import urlparse, parse_qs
    parsed = urlparse(url)
    params = parse_qs(parsed.query)
    tokens = params.get("invite_token", [])
    return tokens[0] if tokens else None


class InviteService:
    def __init__(self, db: Session):
        self.db = db

    async def create_invite(
        self,
        data: CreateInviteDTO,
        created_by: str,
        tenant_slug: str,
        user_access_token: str = "",
    ) -> InviteResponseDTO:
        import httpx
        from api.v1.modules.core.tenants.models import Tenant

        tenant = (
            self.db.query(Tenant)
            .filter(Tenant.slug == tenant_slug, Tenant.is_active == True)
            .first()
        )
        if not tenant:
            raise HTTPException(status_code=404, detail="Tenant no encontrado")

        token_plain = secrets.token_urlsafe(32)
        token_hash = _hash_token(token_plain)
        expires_at = datetime.now(timezone.utc) + timedelta(hours=INVITE_TTL_HOURS)

        from api.v1.modules.core.permissions.models import CompanyRole

        company_role = (
            self.db.query(CompanyRole)
            .filter(
                CompanyRole.id == data.role_id,
                CompanyRole.company_id == data.company_id,
                CompanyRole.is_active == True,
            )
            .first()
        )
        if not company_role:
            raise HTTPException(status_code=404, detail="Rol no encontrado")

        # Crear invite en el Hub usando el token del usuario actual.
        # El usuario debe tener role='admin' en su tenant dentro del Hub.
        # No se requieren credenciales de hub_admin — sin secretos en el .env del cliente.
        hub_invite_token: Optional[str] = None
        invite_url: str = ""
        if not user_access_token:
            logger.error(
                "[invite] user_access_token vacío — no se puede crear el invite en el Hub. "
                "tenant=%s email=%s",
                tenant_slug,
                data.email,
            )
        else:
            try:
                async with httpx.AsyncClient(timeout=10.0) as client:
                    hub_resp = await client.post(
                        f"{settings.HUB_URL}api/v1/hub/invites",
                        json={"email": str(data.email), "tenant_slug": tenant_slug},
                        headers={"Authorization": f"Bearer {user_access_token}"},
                    )
                    if hub_resp.status_code in (200, 201):
                        hub_data = hub_resp.json()
                        hub_invite_token = hub_data.get("invite_token") or _extract_token_from_url(hub_data.get("invite_url", ""))
                        invite_url = hub_data.get("invite_url", "")
                    else:
                        logger.error(
                            "[invite] Hub invite creation falló: status=%s body=%s tenant=%s email=%s",
                            hub_resp.status_code,
                            hub_resp.text[:300],
                            tenant_slug,
                            data.email,
                        )
            except Exception as exc:
                logger.error("[invite] Hub invite creation excepción (non-blocking): %s", exc)

        # Fallback: URL del workspace (Hub) si la creación de invitación en Hub falló
        if not invite_url:
            hub_base = settings.HUB_URL.rstrip("/")
            invite_url = (
                f"{hub_base}/register"
                f"?invite_token={token_plain}"
                f"&tenant={tenant_slug}"
                f"&email={data.email}"
            )

        invite = InviteToken(
            token_hash=token_hash,
            tenant_slug=tenant_slug,
            email=str(data.email),
            role=company_role.code,
            created_by=created_by,
            expires_at=expires_at,
            company_id=data.company_id,
            hub_invite_token=hub_invite_token,
        )
        self.db.add(invite)
        self.db.commit()
        self.db.refresh(invite)

        # Enviar email (best-effort)
        try:
            await self._send_invite_email(
                to_email=str(data.email),
                tenant_name=tenant.name,
                invite_url=invite_url,
            )
        except Exception as exc:
            logger.warning(
                "Invite email send failed (non-blocking): %s — invite_url=%s",
                exc,
                invite_url,
            )

        return InviteResponseDTO(
            id=invite.id,
            email=invite.email,
            role=invite.role,
            expires_at=invite.expires_at,
            invite_url=invite_url,
            created_at=invite.created_at,
        )

    def validate(
        self,
        token_plain: str,
        tenant_slug: str,
        email: Optional[str] = None,
    ) -> InviteValidationResult:
        """Valida el token sin consumirlo. Lanza 403 genérico por seguridad."""
        token_hash = _hash_token(token_plain)
        now = datetime.now(timezone.utc)

        invite = (
            self.db.query(InviteToken)
            .filter(
                InviteToken.token_hash == token_hash,
                InviteToken.tenant_slug == tenant_slug,
                InviteToken.used_at.is_(None),
                InviteToken.expires_at > now,
            )
            .first()
        )

        if not invite:
            raise HTTPException(
                status_code=403,
                detail="Token de invitación inválido o expirado",
            )

        if email and invite.email.lower() != email.lower():
            raise HTTPException(
                status_code=403,
                detail="Token de invitación inválido o expirado",
            )

        return InviteValidationResult(
            email=invite.email,
            role=invite.role,
            invite_id=invite.id,
            tenant_slug=invite.tenant_slug,
            company_id=invite.company_id,
        )

    def consume_by_id(self, invite_id: int) -> None:
        invite = self.db.query(InviteToken).filter(InviteToken.id == invite_id).first()
        if invite:
            invite.used_at = datetime.now(timezone.utc)
            self.db.commit()

    async def _send_invite_email(
        self,
        to_email: str,
        tenant_name: str,
        invite_url: str,
    ) -> None:
        msg = MIMEMultipart("alternative")
        msg["From"] = f"{settings.SMTP_FROM_NAME} <{settings.SMTP_USER}>"
        msg["To"] = to_email
        msg["Subject"] = f"Invitación para unirse a {tenant_name} en Mi Aplicación"

        html = f"""
        <html>
          <body style="font-family: Arial, sans-serif; background: #f3f4f6; padding: 40px 0;">
            <div style="max-width: 600px; margin: 0 auto; background: #ffffff; border-radius: 8px;
                        overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
              <div style="background: #2563eb; padding: 32px 40px;">
                <h1 style="color: #ffffff; margin: 0; font-size: 24px;">Mi Aplicación</h1>
                <p style="color: #bfdbfe; margin: 8px 0 0;">Sistema de gestión aduanal</p>
              </div>
              <div style="padding: 40px;">
                <h2 style="color: #111827; font-size: 20px; margin-top: 0;">
                  Te han invitado a {tenant_name}
                </h2>
                <p style="color: #4b5563; line-height: 1.6;">
                  Has recibido una invitación para unirte a <strong>{tenant_name}</strong>
                  en Mi Aplicación. Haz clic en el botón para crear tu cuenta.
                </p>
                <div style="text-align: center; margin: 32px 0;">
                  <a href="{invite_url}"
                     style="display: inline-block; background: #2563eb; color: #ffffff;
                            text-decoration: none; padding: 14px 32px; border-radius: 6px;
                            font-weight: 600; font-size: 16px;">
                    Aceptar invitación
                  </a>
                </div>
                <p style="color: #6b7280; font-size: 13px; line-height: 1.5;">
                  Este enlace es válido por <strong>48 horas</strong> y es de
                  <strong>un solo uso</strong>.<br>
                  Si no esperabas esta invitación, puedes ignorar este correo.
                </p>
                <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 24px 0;">
                <p style="color: #9ca3af; font-size: 12px;">
                  O copia este enlace en tu navegador:<br>
                  <span style="color: #2563eb; word-break: break-all;">{invite_url}</span>
                </p>
              </div>
            </div>
          </body>
        </html>
        """
        msg.attach(MIMEText(html, "html"))

        ssl_ctx = ssl.create_default_context()
        ssl_ctx.check_hostname = False
        ssl_ctx.verify_mode = ssl.CERT_NONE

        if settings.SMTP_PORT == 465:
            async with aiosmtplib.SMTP(
                hostname=settings.SMTP_HOST,
                port=settings.SMTP_PORT,
                use_tls=True,
                tls_context=ssl_ctx,
            ) as smtp:
                await smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
                await smtp.send_message(msg)
        else:
            async with aiosmtplib.SMTP(
                hostname=settings.SMTP_HOST,
                port=settings.SMTP_PORT,
                tls_context=ssl_ctx,
            ) as smtp:
                await smtp.starttls(tls_context=ssl_ctx)
                await smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
                await smtp.send_message(msg)
