import json

from hiveos.models.chunk import Chunk
from hiveos.models.task import Task
from hiveos.runtime.chunk_runtime import hydrate_from_db_row as hydrate_chunk
from hiveos.runtime.task_runtime import hydrate_from_db_row as hydrate_task


class TaskIngestionService:
    def __init__(self, core):
        self.core = core

    def create_task(self, intent):
        task_name = intent.get("task_name", "unnamed")
        execution_mode = intent.get("execution_mode", "concurrent")
        chunk_specs = intent.get("chunks", [])
        self._validate_chunk_specs(chunk_specs, execution_mode)

        has_conditionals = any("conditional" in chunk for chunk in chunk_specs)
        task_id = Task.create(
            name=task_name,
            execution_mode=execution_mode,
            has_conditionals=has_conditionals,
            session_id=self.core.session_id,
        )

        task_row = Task.get_by_id(task_id)
        task_obj = hydrate_task(
            task_row,
            session_id=self.core.session_id,
            capabilities=intent.get("capability_required", []),
        )
        self.core.task_objects[task_id] = task_obj
        self.core.task_name_map.setdefault(task_obj.name, []).append(task_id)

        previous_chunk_id = None
        for idx, chunk in enumerate(chunk_specs):
            raw_chunk_id = chunk.get("chunk_id", f"C{idx + 1}")
            chunk_id = f"{raw_chunk_id}_{task_id}"
            payload = json.dumps(chunk.get("payload", {}))
            conditional = json.dumps(chunk.get("conditional", {}))
            required_capability = chunk.get("required_capability", intent.get("capability_required", "generic"))

            if execution_mode == "sequential":
                depends_on = previous_chunk_id
            else:
                depends_on = chunk.get("depends_on")
                if depends_on:
                    depends_on = f"{depends_on}_{task_id}"

            ttl = chunk.get("ttl", 1)
            timing = chunk.get("timing", "ticks")
            Chunk.create(
                chunk_id,
                task_id,
                required_capability,
                payload=payload,
                depends_on=depends_on,
                ttl=ttl,
                timing=timing,
                conditional=conditional,
                session_id=self.core.session_id,
            )
            chunk_row = Chunk.get_by_id(chunk_id)
            self.core.chunk_objects[chunk_id] = hydrate_chunk(chunk_row, session_id=self.core.session_id)
            previous_chunk_id = chunk_id if execution_mode == "sequential" else previous_chunk_id

        return task_id

    def _validate_chunk_specs(self, chunk_specs, execution_mode):
        if not isinstance(chunk_specs, list) or not chunk_specs:
            raise ValueError("Task requires at least one chunk")

        if execution_mode == "sequential":
            return

        chunk_ids = []
        for idx, chunk in enumerate(chunk_specs):
            if not isinstance(chunk, dict):
                raise ValueError(f"Chunk at index {idx} must be an object")
            cid = str(chunk.get("chunk_id", f"C{idx + 1}")).strip()
            if not cid:
                raise ValueError(f"Chunk at index {idx} has empty chunk_id")
            chunk_ids.append(cid)

        seen = set()
        duplicates = set()
        for cid in chunk_ids:
            if cid in seen:
                duplicates.add(cid)
            seen.add(cid)
        if duplicates:
            raise ValueError(f"Duplicate chunk_id values are not allowed: {sorted(duplicates)}")

        nodes = set(chunk_ids)
        edges = {}
        for idx, chunk in enumerate(chunk_specs):
            cid = chunk_ids[idx]
            dep = chunk.get("depends_on")
            if dep is None or str(dep).strip() == "":
                edges[cid] = None
                continue
            dep = str(dep).strip()
            if dep not in nodes:
                raise ValueError(f"Chunk '{cid}' depends_on unknown chunk '{dep}'")
            if dep == cid:
                raise ValueError(f"Chunk '{cid}' cannot depend on itself")
            edges[cid] = dep

        visiting = set()
        visited = set()

        def _dfs(node):
            if node in visited:
                return
            if node in visiting:
                raise ValueError(f"Cyclic dependency detected at chunk '{node}'")
            visiting.add(node)
            dep = edges.get(node)
            if dep:
                _dfs(dep)
            visiting.remove(node)
            visited.add(node)

        for node in nodes:
            _dfs(node)
