"""High-level integration configuration and orchestration service."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from sqlalchemy.orm import Session

from app.services import runtime_config
from app.services.integrations.registry import NOTES_PROVIDER_IDS, PROVIDER_META, TASK_PROVIDER_IDS, ProviderRegistry
from app.services.integrations.types import ProviderConnectionResult, UnifiedTask


@dataclass(slots=True)
class IntegrationStatusPayload:
    """Serialized integrations status payload."""

    providers: dict[str, dict[str, Any]]
    default_task_provider: str
    default_notes_provider: str
    risk_notice: str

    def as_dict(self) -> dict[str, Any]:
        return {
            "providers": self.providers,
            "default_task_provider": self.default_task_provider,
            "default_notes_provider": self.default_notes_provider,
            "risk_notice": self.risk_notice,
        }


RISK_NOTICE = (
    "OAuth/token credentials are stored locally on this machine. "
    "Protect local access and rotate tokens if compromised."
)


def _copy_providers() -> dict[str, dict[str, Any]]:
    """Return mutable providers snapshot."""
    snapshot = runtime_config.get_providers_snapshot()
    return {key: dict(value) for key, value in snapshot.items()}


def _normalize_string_dict(raw: dict[str, Any] | None) -> dict[str, str]:
    """Normalize credential text fields."""
    if not isinstance(raw, dict):
        return {}
    normalized: dict[str, str] = {}
    for key, value in raw.items():
        key_text = str(key or "").strip()
        if not key_text:
            continue
        value_text = str(value or "").strip()
        if value_text:
            normalized[key_text] = value_text
    return normalized


def _normalize_settings_dict(raw: dict[str, Any] | None) -> dict[str, Any]:
    """Normalize settings payload preserving non-empty values."""
    if not isinstance(raw, dict):
        return {}
    normalized: dict[str, Any] = {}
    for key, value in raw.items():
        key_text = str(key or "").strip()
        if not key_text:
            continue
        if value is None:
            continue
        if isinstance(value, str):
            text = value.strip()
            if not text:
                continue
            normalized[key_text] = text
            continue
        normalized[key_text] = value
    return normalized


def _sync_legacy_from_providers(providers: dict[str, dict[str, Any]]) -> tuple[str | None, list[str]]:
    """Synchronize legacy top-level fields from provider map."""
    todoist_provider = providers.get("todoist", {})
    todoist_credentials = todoist_provider.get("credentials") if isinstance(todoist_provider, dict) else {}
    todoist_token = None
    if isinstance(todoist_credentials, dict):
        token_raw = todoist_credentials.get("token")
        token_text = str(token_raw or "").strip()
        if token_text:
            todoist_token = token_text

    vault_provider = providers.get("vault_local", {})
    vault_settings = vault_provider.get("settings") if isinstance(vault_provider, dict) else {}
    vault_paths: list[str] = []
    raw_paths = vault_settings.get("paths") if isinstance(vault_settings, dict) else None
    if isinstance(raw_paths, list):
        for item in raw_paths:
            text = str(item or "").strip()
            if text:
                vault_paths.append(text)
    return todoist_token, vault_paths


def _current_defaults() -> tuple[str, str]:
    """Return effective default providers."""
    return runtime_config.get_default_task_provider("todoist"), runtime_config.get_default_notes_provider("vault_local")


def _save_provider_map(
    *,
    providers: dict[str, dict[str, Any]],
    default_task_provider: str | None = None,
    default_notes_provider: str | None = None,
    db: Session | None = None,
) -> None:
    """Persist providers map while preserving language settings."""
    current = runtime_config.get_runtime_config()
    assistant_default_language = current.assistant_default_language if current else "es"
    language_configured = bool(current.language_configured) if current else False
    todoist_token, vault_paths = _sync_legacy_from_providers(providers)
    current_default_task, current_default_notes = _current_defaults()

    runtime_config.save_config(
        todoist_api_token=todoist_token,
        vault_paths=vault_paths,
        assistant_default_language=assistant_default_language,
        language_configured=language_configured,
        providers=providers,
        default_task_provider=default_task_provider or current_default_task,
        default_notes_provider=default_notes_provider or current_default_notes,
        db=db,
    )


def get_integrations_status() -> IntegrationStatusPayload:
    """Return current integrations status view."""
    registry = ProviderRegistry()
    return IntegrationStatusPayload(
        providers=registry.provider_statuses(),
        default_task_provider=registry.default_task_provider(),
        default_notes_provider=registry.default_notes_provider(),
        risk_notice=RISK_NOTICE,
    )


def save_provider_config(
    *,
    provider_id: str,
    enabled: bool | None = None,
    credentials: dict[str, Any] | None = None,
    settings: dict[str, Any] | None = None,
    auth_mode: str | None = None,
    db: Session | None = None,
) -> IntegrationStatusPayload:
    """Create/update one provider config independently."""
    if provider_id not in PROVIDER_META:
        raise LookupError(f"Unknown provider: {provider_id}")

    providers = _copy_providers()
    existing = providers.get(provider_id, {})
    current_credentials = _normalize_string_dict(existing.get("credentials") if isinstance(existing, dict) else None)
    current_settings = _normalize_settings_dict(existing.get("settings") if isinstance(existing, dict) else None)
    incoming_credentials = _normalize_string_dict(credentials)
    incoming_settings = _normalize_settings_dict(settings)

    next_payload = {
        "enabled": bool(enabled) if enabled is not None else bool(existing.get("enabled")),
        "auth_mode": str(auth_mode or existing.get("auth_mode") or PROVIDER_META[provider_id].auth_type).strip().lower(),
        "credentials": {**current_credentials, **incoming_credentials},
        "settings": {**current_settings, **incoming_settings},
        "last_error": None,
    }
    providers[provider_id] = next_payload
    _save_provider_map(providers=providers, db=db)
    return get_integrations_status()


def set_provider_enabled(provider_id: str, *, enabled: bool, db: Session | None = None) -> IntegrationStatusPayload:
    """Enable/disable one provider."""
    if provider_id not in PROVIDER_META:
        raise LookupError(f"Unknown provider: {provider_id}")
    providers = _copy_providers()
    existing = dict(providers.get(provider_id, {}))
    existing["enabled"] = bool(enabled)
    providers[provider_id] = existing
    _save_provider_map(providers=providers, db=db)
    return get_integrations_status()


def disconnect_provider(provider_id: str, *, db: Session | None = None) -> IntegrationStatusPayload:
    """Remove credentials/settings for one provider."""
    if provider_id not in PROVIDER_META:
        raise LookupError(f"Unknown provider: {provider_id}")

    providers = _copy_providers()
    reset = {
        "enabled": False,
        "auth_mode": PROVIDER_META[provider_id].auth_type,
        "credentials": {},
        "settings": {},
        "last_error": None,
    }
    providers[provider_id] = reset
    _save_provider_map(providers=providers, db=db)
    return get_integrations_status()


def _set_provider_last_error(provider_id: str, *, last_error: str | None, db: Session | None = None) -> None:
    """Persist provider last_error field without changing credentials/settings."""
    if provider_id not in PROVIDER_META:
        raise LookupError(f"Unknown provider: {provider_id}")
    providers = _copy_providers()
    existing = dict(providers.get(provider_id, {}))
    existing["last_error"] = str(last_error).strip() if last_error else None
    providers[provider_id] = existing
    _save_provider_map(providers=providers, db=db)


def set_default_providers(
    *,
    default_task_provider: str | None = None,
    default_notes_provider: str | None = None,
    db: Session | None = None,
) -> IntegrationStatusPayload:
    """Persist default providers selection."""
    task_provider = default_task_provider if default_task_provider in TASK_PROVIDER_IDS else None
    notes_provider = default_notes_provider if default_notes_provider in NOTES_PROVIDER_IDS else None
    providers = _copy_providers()
    _save_provider_map(
        providers=providers,
        default_task_provider=task_provider,
        default_notes_provider=notes_provider,
        db=db,
    )
    return get_integrations_status()


def test_provider(provider_id: str, *, db: Session | None = None) -> ProviderConnectionResult:
    """Run minimal provider connectivity check."""
    registry = ProviderRegistry()
    try:
        result = registry.test_provider(provider_id, db=db)
    except Exception as exc:
        _set_provider_last_error(provider_id, last_error=str(exc), db=db)
        if db is not None:
            db.commit()
        raise
    _set_provider_last_error(provider_id, last_error=None if result.ok else result.detail, db=db)
    if db is not None:
        db.commit()
    return result


def list_tasks(
    *,
    provider_id: str | None = None,
    across_all: bool = False,
    include_completed: bool = False,
) -> list[dict[str, Any]]:
    """Return aggregated tasks payload across configured providers."""
    registry = ProviderRegistry()
    tasks = registry.list_tasks(
        provider_id=provider_id,
        across_all=across_all,
        include_completed=include_completed,
    )
    return [item.to_api_dict() for item in tasks]


def create_task(
    *,
    content: str,
    description: str | None = None,
    due_string: str | None = None,
    provider_id: str | None = None,
) -> dict[str, Any]:
    """Create task in target/default provider."""
    registry = ProviderRegistry()
    task = registry.create_task(content=content, description=description, due_string=due_string, provider_id=provider_id)
    return task.to_api_dict()


def complete_task(*, task_id: str, provider_id: str | None = None) -> None:
    """Complete task in target/default provider."""
    registry = ProviderRegistry()
    registry.complete_task(task_id=task_id, provider_id=provider_id)


def create_coverage_tasks(
    *,
    task_titles: list[str],
    description_template: str,
    provider_id: str | None = None,
) -> tuple[list[str], list[str]]:
    """Create task list from coverage gap names."""
    created: list[str] = []
    skipped: list[str] = []
    registry = ProviderRegistry()
    target_provider = provider_id or registry.default_task_provider()

    existing_tasks = registry.list_tasks(provider_id=target_provider, across_all=False)
    existing_norm = {item.content.strip().lower() for item in existing_tasks}
    for title in task_titles:
        norm = title.strip().lower()
        if not norm or norm in existing_norm:
            skipped.append(title)
            continue
        task = registry.create_task(
            provider_id=target_provider,
            content=title,
            description=description_template,
        )
        existing_norm.add(norm)
        created.append(task.content)
    return created, skipped


def search_notes(
    *,
    query: str,
    provider_ids: list[str] | None = None,
    limit: int = 5,
    db: Session | None = None,
) -> list[dict[str, Any]]:
    """Search notes across selected providers."""
    registry = ProviderRegistry()
    hits = registry.search_notes(query=query, provider_ids=provider_ids, limit=limit, db=db)
    return [item.to_dict() for item in hits]


def write_generated_notes(
    *,
    title: str,
    content: str,
    provider_ids: list[str],
    db: Session | None = None,
) -> dict[str, str]:
    """Create note entries in requested providers."""
    registry = ProviderRegistry()
    return registry.append_note_to_targets(title=title, content=content, provider_ids=provider_ids, db=db)


def provider_mention_to_id(message_normalized: str) -> str | None:
    """Resolve provider id from user text mention."""
    text = (message_normalized or "").lower()
    aliases = {
        "todoist": ("todoist",),
        "google_tasks": ("google tasks", "google task", "google"),
        "microsoft_todo": ("microsoft to do", "microsoft todo", "outlook tasks", "microsoft"),
        "trello": ("trello",),
        "github_issues": ("github", "github issues", "issue"),
        "notion": ("notion",),
        "vault_local": ("vault", "notes"),
    }
    for provider_id, markers in aliases.items():
        if any(marker in text for marker in markers):
            return provider_id
    return None
