"""
Cloud Team Memory — sync project context across team via Supabase.

Supabase schema (already created):
    teams (id, name, invite_code, owner_id, created_at)
    team_members (team_id, user_id, joined_at)
    team_memory (id, team_id, project_id, index_data jsonb, updated_at, updated_by)
    projects (id, user_id, name, repo_id, source_root, file_count, indexed_at)

Config: ~/.kodara/config.json
    { "team_id": "uuid-of-current-team", ... }
"""

from __future__ import annotations

import json
import secrets
import string
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

from kodara.cloud.client import get_client, get_config_path, _load_config, _atomic_write
from kodara.memory.store import KodaraIndex, MemoryStore


# ── Helpers ───────────────────────────────────────────────────────────────────

def _generate_invite_code(length: int = 8) -> str:
    """Generate a random alphanumeric invite code."""
    alphabet = string.ascii_lowercase + string.digits
    return "".join(secrets.choice(alphabet) for _ in range(length))


def _load_team_config() -> dict:
    """Read ~/.kodara/config.json and return it (creates defaults if missing)."""
    return _load_config()


def _save_team_id(team_id: str) -> None:
    """Persist team_id into ~/.kodara/config.json without touching other keys."""
    cfg_path = get_config_path()
    cfg = _load_team_config()
    cfg["team_id"] = team_id
    _atomic_write(cfg_path, cfg)


def get_saved_team_id() -> Optional[str]:
    """Return the team_id stored in ~/.kodara/config.json, or None."""
    return _load_team_config().get("team_id")


# ── Dataclass ─────────────────────────────────────────────────────────────────

@dataclass
class TeamInfo:
    id: str
    name: str
    invite_code: str
    member_count: int


# ── Core functions ────────────────────────────────────────────────────────────

def create_team(name: str, user_id: str) -> TeamInfo:
    """
    Create a new team in Supabase and add the creator as a member.

    Returns:
        TeamInfo with id, name, invite_code, member_count=1.

    Raises:
        ImportError: if supabase package is not installed.
        RuntimeError: if Supabase returns an error.
    """
    client = get_client()
    invite_code = _generate_invite_code(8)

    # Insert team record
    result = (
        client.table("teams")
        .insert({
            "name": name,
            "invite_code": invite_code,
            "owner_id": user_id,
            "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        })
        .execute()
    )

    if not result.data:
        raise RuntimeError(f"Failed to create team '{name}'")

    team = result.data[0]
    team_id = team["id"]

    # Add creator as member
    client.table("team_members").insert({
        "team_id": team_id,
        "user_id": user_id,
        "joined_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    }).execute()

    # Persist team_id locally
    _save_team_id(team_id)

    return TeamInfo(
        id=team_id,
        name=team["name"],
        invite_code=team["invite_code"],
        member_count=1,
    )


def join_team(invite_code: str, user_id: str) -> TeamInfo:
    """
    Find a team by invite_code and add the user as a member.

    Returns:
        TeamInfo for the joined team.

    Raises:
        ValueError: if no team found with this invite_code.
        RuntimeError: if Supabase returns an error.
    """
    client = get_client()

    # Look up team by invite code
    result = (
        client.table("teams")
        .select("*")
        .eq("invite_code", invite_code)
        .execute()
    )

    if not result.data:
        raise ValueError(f"No team found with invite code: {invite_code}")

    team = result.data[0]
    team_id = team["id"]

    # Add member (upsert — safe if already a member)
    client.table("team_members").upsert({
        "team_id": team_id,
        "user_id": user_id,
        "joined_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    }).execute()

    # Count members
    members_result = (
        client.table("team_members")
        .select("user_id", count="exact")
        .eq("team_id", team_id)
        .execute()
    )
    member_count = getattr(members_result, "count", None) or len(members_result.data or [])

    # Persist team_id locally
    _save_team_id(team_id)

    return TeamInfo(
        id=team_id,
        name=team["name"],
        invite_code=team["invite_code"],
        member_count=member_count,
    )


def push_to_team(team_id: str, index: KodaraIndex, user_id: str) -> None:
    """
    Push the current KodaraIndex to team_memory in Supabase.

    Steps:
      1. Ensure project row exists in projects table (upsert by repo_id + user_id).
      2. Upsert team_memory row for (team_id, project_id).

    Raises:
        RuntimeError: if Supabase operations fail.
    """
    from dataclasses import asdict

    client = get_client()

    # 1. Upsert project record
    project_payload = {
        "user_id": user_id,
        "name": index.display_name,
        "repo_id": index.repo_id,
        "source_root": index.source_root,
        "file_count": index.file_count,
        "indexed_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(index.indexed_at)),
    }

    proj_result = (
        client.table("projects")
        .upsert(project_payload, on_conflict="user_id,repo_id")
        .execute()
    )

    if not proj_result.data:
        raise RuntimeError("Failed to upsert project record")

    project_id = proj_result.data[0]["id"]

    # 2. Serialize index to JSON-serialisable dict
    index_data = asdict(index)

    # 3. Upsert team_memory
    memory_payload = {
        "team_id": team_id,
        "project_id": project_id,
        "index_data": index_data,
        "updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "updated_by": user_id,
    }

    client.table("team_memory").upsert(
        memory_payload, on_conflict="team_id,project_id"
    ).execute()


def pull_from_team(team_id: str, root: Path) -> KodaraIndex:
    """
    Pull the latest team_memory for this project from Supabase.

    Matches by project name derived from root directory name.
    Saves the index to .kodara/ via MemoryStore.

    Returns:
        The restored KodaraIndex.

    Raises:
        ValueError: if no matching team memory found for this project.
        RuntimeError: if Supabase operations fail.
    """
    from kodara.memory.store import INDEX_VERSION

    client = get_client()
    project_name = root.name

    # Find team_memory rows for this team, join with projects
    result = (
        client.table("team_memory")
        .select("*, projects(name, repo_id, source_root, file_count)")
        .eq("team_id", team_id)
        .execute()
    )

    if not result.data:
        raise ValueError(f"No team memory found for team {team_id}")

    # Find a record matching the project name
    local_repo_id = MemoryStore.repo_id_from_path(root)
    matching = None
    for row in result.data:
        proj = row.get("projects") or {}
        if proj.get("name") == project_name or proj.get("repo_id") == local_repo_id:
            matching = row
            break

    if matching is None:
        # Fallback: use the most recently updated record
        matching = max(result.data, key=lambda r: r.get("updated_at", ""))

    index_data = matching["index_data"]

    # Reconstruct KodaraIndex
    index = KodaraIndex(
        repo_id=index_data.get("repo_id", local_repo_id),
        display_name=index_data.get("display_name", project_name),
        source_root=index_data.get("source_root", str(root)),
        indexed_at=float(index_data.get("indexed_at", time.time())),
        index_version=INDEX_VERSION,
        file_count=int(index_data.get("file_count", 0)),
        language_counts=index_data.get("language_counts", {}),
        modules=index_data.get("modules", {}),
        graph_edges=index_data.get("graph_edges", []),
        file_hashes=index_data.get("file_hashes", {}),
    )

    # Persist locally
    store = MemoryStore(root)
    store.init_dir()
    store.save(index)

    return index


def get_team_info(user_id: str) -> list[TeamInfo]:
    """
    Return a list of all teams the user belongs to.

    Returns:
        List of TeamInfo objects (empty list if user has no teams).
    """
    client = get_client()

    # Get all team memberships for this user
    memberships = (
        client.table("team_members")
        .select("team_id")
        .eq("user_id", user_id)
        .execute()
    )

    if not memberships.data:
        return []

    team_ids = [m["team_id"] for m in memberships.data]

    # Fetch team details
    teams_result = (
        client.table("teams")
        .select("*")
        .in_("id", team_ids)
        .execute()
    )

    if not teams_result.data:
        return []

    result = []
    for team in teams_result.data:
        # Count members for each team
        count_result = (
            client.table("team_members")
            .select("user_id", count="exact")
            .eq("team_id", team["id"])
            .execute()
        )
        member_count = getattr(count_result, "count", None) or len(count_result.data or [])

        result.append(TeamInfo(
            id=team["id"],
            name=team["name"],
            invite_code=team["invite_code"],
            member_count=member_count,
        ))

    return result
