defmodule BDS.AI.ChatTools do
  @moduledoc false

  import Ecto.Query

  alias BDS.AI.Chat
  alias BDS.AI.ChatToolQueryHelpers, as: QueryHelpers
  alias BDS.AI.ChatToolSchemas, as: Schemas
  alias BDS.Media, as: MediaContext
  alias BDS.Media.Media
  alias BDS.MCP.Queries
  alias BDS.Posts, as: PostsContext
  alias BDS.Posts.Post
  alias BDS.Posts.PostMedia
  alias BDS.Projects.Project
  alias BDS.Repo

  @spec execute(String.t(), map(), String.t() | nil) :: map()
  def execute("blog_stats", _arguments, project_id) do
    execute("get_blog_stats", %{}, project_id)
  end

  def execute("get_blog_stats", _arguments, project_id) do
    project_id = project_id || active_project_id()

    %{
      post_count:
        Repo.aggregate(from(post in Post, where: post.project_id == ^project_id), :count, :id),
      media_count:
        Repo.aggregate(from(media in Media, where: media.project_id == ^project_id), :count, :id),
      tag_count: Chat.count_distinct_string_list(Post, :tags, project_id),
      category_count: Chat.count_distinct_string_list(Post, :categories, project_id)
    }
  end

  def execute("check_term", arguments, project_id) do
    project_id = project_id || active_project_id()
    term = normalize_term(arguments["term"])

    posts = Repo.all(from post in Post, where: post.project_id == ^project_id)

    tag_post_count =
      Enum.count(posts, fn post ->
        Enum.any?(post.tags || [], &(normalize_term(&1) == term))
      end)

    category_post_count =
      Enum.count(posts, fn post ->
        Enum.any?(post.categories || [], &(normalize_term(&1) == term))
      end)

    %{
      is_category: category_post_count > 0,
      category_post_count: category_post_count,
      is_tag: tag_post_count > 0,
      tag_post_count: tag_post_count
    }
  end

  def execute("search_posts", arguments, project_id) do
    project_id = project_id || active_project_id()
    filters = QueryHelpers.search_filters(arguments)

    QueryHelpers.search_result(
      project_id,
      arguments["query"] || "",
      filters,
      &Queries.post_summary/1
    )
  end

  def execute("read_post_by_slug", arguments, project_id) do
    project_id = project_id || active_project_id()

    case Repo.get_by(Post, project_id: project_id, slug: arguments["slug"]) do
      %Post{} = post -> %{post: Queries.post_detail(post)}
      nil -> %{error: "not_found"}
    end
  end

  def execute("read_post", arguments, project_id) do
    project_id = project_id || active_project_id()

    with_post(arguments, project_id, %{success: false, error: "not_found"}, fn post ->
      %{post: Queries.post_detail(post)}
    end)
  end

  def execute("list_posts", arguments, project_id) do
    project_id = project_id || active_project_id()
    limit = QueryHelpers.normalize_limit(arguments["limit"])
    offset = QueryHelpers.normalize_offset(arguments["offset"])
    filters = QueryHelpers.search_filters(arguments) |> Map.merge(%{limit: limit, offset: offset})

    QueryHelpers.search_result(project_id, "", filters, fn post ->
      post
      |> Queries.post_summary()
      |> Map.put("url", "/posts/#{post.slug}")
      |> Map.put("updated_at", post.updated_at)
    end)
  end

  def execute("list_media", arguments, project_id) do
    project_id = project_id || active_project_id()
    limit = QueryHelpers.normalize_limit(arguments["limit"])

    Repo.all(
      from(media in Media,
        where: media.project_id == ^project_id,
        order_by: [desc: media.updated_at],
        limit: ^limit,
        select: %{
          id: media.id,
          title: media.title,
          mime_type: media.mime_type,
          filename: media.filename,
          updated_at: media.updated_at
        }
      )
    )
  end

  def execute("get_media", arguments, project_id) do
    project_id = project_id || active_project_id()

    with_media(arguments, project_id, %{success: false, error: "not_found"}, fn media ->
      %{media: media_summary(media)}
    end)
  end

  def execute("view_image", arguments, project_id) do
    project_id = project_id || active_project_id()
    media_id = arguments["mediaId"] || arguments["media_id"]
    size = arguments["size"] || "medium"

    case Repo.get_by(Media, id: media_id, project_id: project_id) do
      %Media{mime_type: "image/" <> _rest} = media ->
        case thumbnail_data_url(project_id, media, size) do
          nil -> %{success: false, error: "thumbnail_not_available"}
          data_url -> %{success: true, media: media_summary(media), data_url: data_url}
        end

      %Media{} = media ->
        %{success: false, error: "not_image", mime_type: media.mime_type}

      nil ->
        %{success: false, error: "not_found"}
    end
  end

  def execute("update_post_metadata", arguments, project_id) do
    project_id = project_id || active_project_id()
    post_id = arguments["postId"] || arguments["post_id"]

    with %Post{} <- Repo.get_by(Post, id: post_id, project_id: project_id),
         attrs <- metadata_attrs(arguments, ["title", "excerpt", "tags", "categories"]),
         false <- attrs == %{},
         {:ok, post} <- PostsContext.update_post(post_id, attrs) do
      %{success: true, post: Queries.post_detail(post)}
    else
      nil -> %{success: false, error: "not_found"}
      true -> %{success: false, error: "no_updates_provided"}
      {:error, reason} -> %{success: false, error: inspect(reason)}
    end
  end

  def execute("update_media_metadata", arguments, project_id) do
    project_id = project_id || active_project_id()
    media_id = arguments["mediaId"] || arguments["media_id"]

    with %Media{} <- Repo.get_by(Media, id: media_id, project_id: project_id),
         attrs <- metadata_attrs(arguments, ["title", "alt", "caption", "tags"]),
         false <- attrs == %{},
         {:ok, media} <- MediaContext.update_media(media_id, attrs) do
      %{success: true, media: media_summary(media)}
    else
      nil -> %{success: false, error: "not_found"}
      true -> %{success: false, error: "no_updates_provided"}
      {:error, reason} -> %{success: false, error: inspect(reason)}
    end
  end

  def execute("list_tags", _arguments, project_id) do
    project_id = project_id || active_project_id()

    %{
      tags: counted_terms(project_id, :tags),
      count: length(counted_terms(project_id, :tags))
    }
  end

  def execute("list_categories", _arguments, project_id) do
    project_id = project_id || active_project_id()

    %{
      categories: counted_terms(project_id, :categories),
      count: length(counted_terms(project_id, :categories))
    }
  end

  def execute("count_posts", arguments, project_id) do
    project_id = project_id || active_project_id()
    group_by = List.wrap(arguments["groupBy"] || arguments["group_by"]) |> Enum.map(&to_string/1)
    result = QueryHelpers.search_all_counted_posts(project_id, arguments)

    groups =
      result.posts
      |> Enum.flat_map(&Queries.group_rows(&1, group_by))
      |> Enum.group_by(& &1, fn _row -> 1 end)
      |> Enum.map(fn {row, counts} -> Map.put(row, "count", length(counts)) end)
      |> Enum.sort_by(&Map.to_list/1)

    %{groups: groups, total_posts: result.total}
  end

  def execute("get_post_backlinks", arguments, project_id) do
    project_id = project_id || active_project_id()

    with_post(arguments, project_id, %{success: false, error: "not_found"}, fn post ->
      %{success: true, post_id: post.id, linked_by: Queries.linked_posts(post.id, :incoming)}
    end)
  end

  def execute("get_post_outlinks", arguments, project_id) do
    project_id = project_id || active_project_id()

    with_post(arguments, project_id, %{success: false, error: "not_found"}, fn post ->
      %{success: true, post_id: post.id, links_to: Queries.linked_posts(post.id, :outgoing)}
    end)
  end

  def execute("get_post_media", arguments, project_id) do
    project_id = project_id || active_project_id()

    with_post(arguments, project_id, %{success: false, error: "not_found"}, fn post ->
      %{success: true, post_id: post.id, media: post_media(project_id, post.id)}
    end)
  end

  def execute("get_media_posts", arguments, project_id) do
    project_id = project_id || active_project_id()

    with_media(arguments, project_id, %{success: false, error: "not_found"}, fn media ->
      %{success: true, media_id: media.id, posts: media_posts(media.id)}
    end)
  end

  def execute("render_table", arguments, _project_id) do
    %{
      type: "table",
      title: arguments["title"],
      columns: arguments["columns"] || [],
      rows: arguments["rows"] || []
    }
  end

  def execute("render_chart", arguments, _project_id) do
    %{
      type: "chart",
      title: arguments["title"],
      chart_type: arguments["chartType"] || arguments["chart_type"] || "bar",
      series: arguments["series"] || []
    }
  end

  def execute("render_form", arguments, _project_id) do
    %{
      type: "form",
      title: arguments["title"],
      fields: arguments["fields"] || [],
      submit_label: arguments["submit_label"] || arguments["submitLabel"],
      submit_action: arguments["submit_action"] || arguments["submitAction"]
    }
  end

  def execute("render_card", arguments, _project_id) do
    %{
      type: "card",
      title: arguments["title"],
      subtitle: arguments["subtitle"],
      body: arguments["body"],
      actions: arguments["actions"] || []
    }
  end

  def execute("render_metric", arguments, _project_id) do
    %{
      type: "metric",
      label: arguments["label"],
      value: arguments["value"]
    }
  end

  def execute("render_list", arguments, _project_id) do
    %{
      type: "list",
      title: arguments["title"],
      items: arguments["items"] || []
    }
  end

  def execute("render_tabs", arguments, _project_id) do
    %{
      type: "tabs",
      title: arguments["title"],
      tabs: arguments["tabs"] || []
    }
  end

  def execute("render_mindmap", arguments, _project_id) do
    %{
      type: "mindmap",
      title: arguments["title"],
      nodes: arguments["nodes"] || []
    }
  end

  def execute(name, _arguments, _project_id) do
    %{error: "unknown_tool", name: name}
  end

  @spec available_specs(String.t() | nil, map()) :: [map()]
  def available_specs(project_id, capabilities) do
    if capabilities.supports_tool_calls do
      project_tools =
        if is_binary(project_id) do
          [
            %{
              name: "blog_stats",
              spec:
                Schemas.tool_spec("blog_stats", "Return aggregate blog statistics", %{
                  "type" => "object",
                  "properties" => %{}
                })
            },
            %{
              name: "get_blog_stats",
              spec:
                Schemas.tool_spec(
                  "get_blog_stats",
                  "Get comprehensive blog statistics: total posts, media count, unique tag count, and unique category count. Use this first when you need to understand the scope of the data.",
                  %{"type" => "object", "properties" => %{}}
                )
            },
            %{
              name: "check_term",
              spec:
                Schemas.tool_spec(
                  "check_term",
                  "Check whether a term exists as a category, tag, or both. Returns post counts for each. Use before search_posts or list_posts when unsure whether a term is a category or tag.",
                  %{
                    "type" => "object",
                    "properties" => %{"term" => %{"type" => "string"}},
                    "required" => ["term"]
                  }
                )
            },
            %{
              name: "search_posts",
              spec:
                Schemas.tool_spec(
                  "search_posts",
                  "Search blog posts using full-text search. Can filter by category, tags, language, missing translation language, year, month, or status. Returns paginated concrete post data with titles, slugs, tags, categories, backlinks, and links_to.",
                  Schemas.post_search_schema(true)
                )
            },
            %{
              name: "read_post",
              spec:
                Schemas.tool_spec(
                  "read_post",
                  "Read full content and metadata of a specific blog post by ID. Includes backlinks, links_to, tags, categories, excerpt, status, language, and available languages.",
                  %{
                    "type" => "object",
                    "properties" => %{"postId" => %{"type" => "string"}},
                    "required" => ["postId"]
                  }
                )
            },
            %{
              name: "read_post_by_slug",
              spec:
                Schemas.tool_spec(
                  "read_post_by_slug",
                  "Read full content and metadata of a specific blog post by slug. Includes backlinks, links_to, tags, categories, excerpt, status, language, and available languages.",
                  %{
                    "type" => "object",
                    "properties" => %{"slug" => %{"type" => "string"}},
                    "required" => ["slug"]
                  }
                )
            },
            %{
              name: "list_posts",
              spec:
                Schemas.tool_spec(
                  "list_posts",
                  "List blog posts with optional filtering by status, category, tags, language, year, or month. Returns paginated concrete post data with titles, slugs, URLs, statuses, tags, categories, backlinks, and links_to. Use for recent, latest, top, or title-list requests.",
                  Schemas.post_search_schema(false)
                )
            },
            %{
              name: "get_media",
              spec:
                Schemas.tool_spec(
                  "get_media",
                  "Get information about a specific media file by ID, including title, alt text, caption, tags, filename, MIME type, dimensions, and update time.",
                  Schemas.media_id_schema()
                )
            },
            %{
              name: "list_media",
              spec:
                Schemas.tool_spec(
                  "list_media",
                  "List concrete media data in the active project, including titles, filenames, MIME types, and update times.",
                  Schemas.limit_schema()
                )
            },
            %{
              name: "view_image",
              spec:
                Schemas.tool_spec(
                  "view_image",
                  "View an image thumbnail as a local data URL for visual inspection. Only works with image media files.",
                  Schemas.media_id_schema(%{
                    "size" => %{"type" => "string", "enum" => ["small", "medium", "large"]}
                  })
                )
            },
            %{
              name: "update_post_metadata",
              spec:
                Schemas.tool_spec(
                  "update_post_metadata",
                  "Update metadata for a blog post: title, excerpt, tags, or categories. Does not update post body content.",
                  Schemas.update_post_metadata_schema()
                )
            },
            %{
              name: "update_media_metadata",
              spec:
                Schemas.tool_spec(
                  "update_media_metadata",
                  "Update metadata for a media file: title, alt text, caption, or tags.",
                  Schemas.update_media_metadata_schema()
                )
            },
            %{
              name: "list_tags",
              spec:
                Schemas.tool_spec(
                  "list_tags",
                  "List all tags used across blog posts with post counts.",
                  %{
                    "type" => "object",
                    "properties" => %{}
                  }
                )
            },
            %{
              name: "list_categories",
              spec:
                Schemas.tool_spec(
                  "list_categories",
                  "List all categories used across blog posts with post counts.",
                  %{"type" => "object", "properties" => %{}}
                )
            },
            %{
              name: "count_posts",
              spec:
                Schemas.tool_spec(
                  "count_posts",
                  "Count posts grouped by dimensions such as year, month, tag, category, or status. Use for analytics, distributions, and heat maps without transferring full post content.",
                  Schemas.count_posts_schema()
                )
            },
            %{
              name: "get_post_backlinks",
              spec:
                Schemas.tool_spec(
                  "get_post_backlinks",
                  "Get all posts that link to a specific post.",
                  Schemas.post_id_schema()
                )
            },
            %{
              name: "get_post_outlinks",
              spec:
                Schemas.tool_spec(
                  "get_post_outlinks",
                  "Get all posts that a specific post links to.",
                  Schemas.post_id_schema()
                )
            },
            %{
              name: "get_post_media",
              spec:
                Schemas.tool_spec(
                  "get_post_media",
                  "Get media files linked to a specific post.",
                  Schemas.post_id_schema()
                )
            },
            %{
              name: "get_media_posts",
              spec:
                Schemas.tool_spec(
                  "get_media_posts",
                  "Get posts that use a specific media file.",
                  Schemas.media_id_schema()
                )
            }
          ]
        else
          []
        end

      project_tools ++
        [
          %{
            name: "render_card",
            spec:
              Schemas.tool_spec(
                "render_card",
                "Render an information card in the chat UI. Use this for displaying a summary, highlight, or actionable item.",
                Schemas.render_card_schema()
              )
          },
          %{
            name: "render_table",
            spec:
              Schemas.tool_spec(
                "render_table",
                "Render a data table in the chat UI. Use this when the user asks for tabular data, comparisons, or structured information.",
                Schemas.render_table_schema()
              )
          },
          %{
            name: "render_chart",
            spec:
              Schemas.tool_spec(
                "render_chart",
                "Render an interactive chart in the chat UI. Use this when the user asks for a chart, graph, or data visualization. Supports bar, stacked-bar, line, area, pie, donut, and heatmap charts. Use stacked-bar for multi-segment bars and heatmap for grid/matrix visualizations.",
                Schemas.render_chart_schema()
              )
          },
          %{
            name: "render_form",
            spec:
              Schemas.tool_spec(
                "render_form",
                "Render an interactive form in the chat UI. Use this when you need to collect structured input from the user.",
                Schemas.render_form_schema()
              )
          },
          %{
            name: "render_metric",
            spec:
              Schemas.tool_spec(
                "render_metric",
                "Render a single metric/KPI display in the chat UI. Use this for showing a single important value with a label.",
                Schemas.render_metric_schema()
              )
          },
          %{
            name: "render_list",
            spec:
              Schemas.tool_spec(
                "render_list",
                "Render a list of items in the chat UI. Use this for displaying bullet-point style lists, checklists, or simple enumerations.",
                Schemas.render_list_schema()
              )
          },
          %{
            name: "render_tabs",
            spec:
              Schemas.tool_spec(
                "render_tabs",
                "Render a tabbed interface in the chat UI. Use this to organize information into multiple tabs that the user can switch between.",
                Schemas.render_tabs_schema()
              )
          },
          %{
            name: "render_mindmap",
            spec:
              Schemas.tool_spec(
                "render_mindmap",
                "Render a mind map diagram in the chat UI. Use this when the user asks for a mind map, concept map, topic tree, brainstorming diagram, or hierarchical overview of ideas.",
                Schemas.render_mindmap_schema()
              )
          }
        ]
    else
      []
    end
  end

  defp with_post(arguments, project_id, not_found_result, success_fun) do
    case Repo.get_by(Post,
           id: arguments["postId"] || arguments["post_id"],
           project_id: project_id
         ) do
      %Post{} = post -> success_fun.(post)
      nil -> not_found_result
    end
  end

  defp with_media(arguments, project_id, not_found_result, success_fun) do
    case Repo.get_by(Media,
           id: arguments["mediaId"] || arguments["media_id"],
           project_id: project_id
         ) do
      %Media{} = media -> success_fun.(media)
      nil -> not_found_result
    end
  end

  defp counted_terms(project_id, field) do
    Repo.all(
      from post in Post, where: post.project_id == ^project_id, select: field(post, ^field)
    )
    |> List.flatten()
    |> Enum.reject(&blank?/1)
    |> Enum.frequencies()
    |> Enum.map(fn {term, count} -> %{name: term, count: count} end)
    |> Enum.sort_by(&String.downcase(to_string(&1.name)))
  end

  defp metadata_attrs(arguments, keys) do
    Enum.reduce(keys, %{}, fn key, acc ->
      BDS.MapUtils.maybe_put(
        acc,
        BDS.MapUtils.safe_atomize_key(key),
        BDS.MapUtils.blank_to_nil(arguments[key])
      )
    end)
  end

  defp media_summary(%Media{} = media) do
    %{
      id: media.id,
      filename: media.filename,
      original_name: media.original_name,
      mime_type: media.mime_type,
      size: media.size,
      width: media.width,
      height: media.height,
      title: media.title,
      alt: media.alt,
      caption: media.caption,
      author: media.author,
      language: media.language,
      tags: media.tags || [],
      created_at: media.created_at,
      updated_at: media.updated_at
    }
  end

  defp post_media(project_id, post_id) do
    Repo.all(
      from media in Media,
        join: post_media in PostMedia,
        on: post_media.media_id == media.id,
        where: post_media.project_id == ^project_id and post_media.post_id == ^post_id,
        order_by: [asc: post_media.sort_order, asc: media.updated_at]
    )
    |> Enum.map(&media_summary/1)
  end

  defp media_posts(media_id) do
    MediaContext.list_linked_posts(media_id)
    |> Enum.map(fn post ->
      %{"id" => post.post_id, "title" => post.title, "sort_order" => post.sort_order}
    end)
  end

  defp thumbnail_data_url(project_id, media, size) do
    project = Repo.get!(Project, project_id)
    size_key = thumbnail_size(size)
    relative_path = MediaContext.thumbnail_paths(media)[size_key]
    absolute_path = Path.join(project.data_path, relative_path || "")

    with true <- is_binary(relative_path),
         true <- File.exists?(absolute_path),
         {:ok, binary} <- File.read(absolute_path) do
      "data:#{thumbnail_mime(absolute_path)};base64," <> Base.encode64(binary)
    else
      _other -> nil
    end
  end

  defp thumbnail_size("small"), do: :small
  defp thumbnail_size("large"), do: :large
  defp thumbnail_size(_size), do: :medium

  defp thumbnail_mime(path) do
    case Path.extname(path) |> String.downcase() do
      ".jpg" -> "image/jpeg"
      ".jpeg" -> "image/jpeg"
      ".png" -> "image/png"
      ".webp" -> "image/webp"
      _other -> "application/octet-stream"
    end
  end

  defp blank?(value), do: is_nil(value) or String.trim(to_string(value)) == ""

  defp normalize_term(value), do: value |> to_string() |> String.downcase()

  defp active_project_id do
    Repo.one(from(project in Project, where: project.is_active == true, select: project.id))
  end
end
