import ImageUploader from "./image-uploader";
import i18next from "i18next";
import { type App, Notice, requestUrl, type TFile } from "obsidian";
import { randomUUID } from "src/utils/id";
import markdownIt from "src/utils/markdown";
import { slugify } from "transliteration";
import type { HaloSetting, HaloSite } from "../settings";
import { extractImageReferences, getAbsolutePath, replaceImagePaths } from "../utils/image";

class HaloService {
  private readonly site: HaloSite;
  private readonly app: App;
  private readonly settings: HaloSetting;
  private readonly headers: Record<string, string> = {};

  constructor(app: App, settings: HaloSetting, site: HaloSite) {
    this.app = app;
    this.settings = settings;
    this.site = site;

    this.headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${site.token}`,
    };
  }

  public async getPost(name: string): Promise<{ post: Post; content: Content } | undefined> {
    try {
      const post = (await requestUrl({
        url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}`,
        headers: this.headers,
      }).json) as Post;

      const snapshot = (await requestUrl({
        url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}/draft?patched=true`,
        headers: this.headers,
      }).json) as Snapshot;

      const { "content.halo.run/patched-content": patchedContent, "content.halo.run/patched-raw": patchedRaw } =
        snapshot.metadata.annotations || {};

      const { rawType } = snapshot.spec || {};

      const content: Content = {
        content: patchedContent,
        raw: patchedRaw,
        rawType,
      };

      return Promise.resolve({
        post,
        content,
      });
    } catch (error) {
      return Promise.resolve(undefined);
    }
  }

  public async publishPost(): Promise<void> {
    const { activeEditor } = this.app.workspace;

    if (!activeEditor || !activeEditor.file) {
      return;
    }

    const imageUploader = new ImageUploader(this.site.url, this.site.token);

    let params: Post = {
      apiVersion: "content.halo.run/v1alpha1",
      kind: "Post",
      metadata: {
        annotations: {},
        name: "",
      },
      spec: {
        allowComment: true,
        baseSnapshot: "",
        categories: [],
        cover: "",
        deleted: false,
        excerpt: {
          autoGenerate: true,
          raw: "",
        },
        headSnapshot: "",
        htmlMetas: [],
        owner: "",
        pinned: false,
        priority: 0,
        publish: false,
        publishTime: "",
        releaseSnapshot: "",
        slug: "",
        tags: [],
        template: "",
        title: "",
        visible: "PUBLIC",
      },
    };

    let content: Content = {
      rawType: "markdown",
      raw: "",
      content: "",
    };

    const md = await this.app.vault.read(activeEditor.file);
    const matterData = this.app.metadataCache.getFileCache(activeEditor.file)?.frontmatter;
    const frontmatterPosition = this.app.metadataCache.getFileCache(activeEditor.file)?.frontmatterPosition;

    const raw = frontmatterPosition ? md.slice(frontmatterPosition?.end.offset) : md;

    // 检测并上传本地图片
    let processedRaw = raw;
    if (this.settings.imageUpload?.enabled) {
      console.log("[HaloService] 图片上传功能已启用");

      const imageReferences = extractImageReferences(raw);
      console.log(`[HaloService] 检测到 ${imageReferences.length} 个图片引用`);

      if (imageReferences.length > 0) {
        const localImages = imageReferences.filter((ref) => !ref.path.startsWith("http://") && !ref.path.startsWith("https://") && !ref.path.startsWith("data:"));
        console.log(`[HaloService] 其中 ${localImages.length} 个是本地图片`);

        if (localImages.length > 0) {
          new Notice(`检测到 ${localImages.length} 个本地图片，正在上传...`);

          const absolutePaths = localImages
            .map((ref) => ({
              original: ref.path,
              absolute: getAbsolutePath(this.app.vault, ref.path, activeEditor.file.path),
            }))
            .filter((item) => item.absolute !== null) as { original: string; absolute: string }[];
          console.log(`[HaloService] 其中 ${absolutePaths.length} 个图片可以解析为绝对路径`);

          if (absolutePaths.length > 0) {
            console.log(`[HaloService] 开始上传 ${absolutePaths.length} 个图片到 ${this.site.url}`);
            const pathMapping = await imageUploader.uploadImages(absolutePaths.map((item) => item.absolute), this.app.vault);
            console.log(`[HaloService] 上传完成，成功 ${pathMapping.size} 个`);

            if (pathMapping.size > 0) {
              const mapping = new Map<string, string>();
              absolutePaths.forEach((item) => {
                const remoteUrl = pathMapping.get(item.absolute);
                if (remoteUrl) {
                  mapping.set(item.original, remoteUrl);
                }
              });

              processedRaw = replaceImagePaths(processedRaw, mapping);

              const successCount = mapping.size;
              const failCount = absolutePaths.length - successCount;

              if (failCount === 0) {
                new Notice(`✓ 图片上传成功 (${successCount}/${absolutePaths.length})`);
              } else {
                new Notice(`⚠ 部分图片上传失败 (成功: ${successCount}, 失败: ${failCount})`);
              }
            } else {
              new Notice("⚠ 图片上传失败，保留原始路径");
            }
          }
        }
      }
    } else {
      console.log("[HaloService] 图片上传功能未启用");
    }

    // 检查站点 URL
    if (matterData?.halo?.site && matterData.halo.site !== this.site.url) {
      new Notice(i18next.t("service.error_site_not_match"));
      return;
    }

    // 如果已发布，获取现有文章信息
    if (matterData?.halo?.name) {
      const post = await this.getPost(matterData.halo.name);

      if (post) {
        params = post.post;
        content = post.content;
      }
    }

    content.raw = processedRaw;
    content.content = markdownIt.render(processedRaw);

    // 恢复元数据
    if (matterData?.title) {
      params.spec.title = matterData.title;
    }

    if (matterData?.slug) {
      params.spec.slug = matterData.slug;
    }

    if (matterData?.excerpt) {
      params.spec.excerpt.raw = matterData.excerpt;
      params.spec.excerpt.autoGenerate = false;
    }

    if (matterData?.cover) {
      params.spec.cover = matterData.cover;
    }

    if (matterData?.categories) {
      const categoryNames = await this.getCategoryNames(matterData.categories);
      params.spec.categories = categoryNames;
    }

    if (matterData?.tags) {
      const tagNames = await this.getTagNames(matterData.tags);
      params.spec.tags = tagNames;
    }

    try {
      // 设置标题和 slug
      params.spec.title = matterData?.title || activeEditor.file.basename;
      params.spec.slug = matterData?.slug || slugify(params.spec.title, { trim: true });

      // 设置内容注解
      params.metadata.annotations = {
        ...params.metadata.annotations,
        "content.halo.run/content-json": JSON.stringify(content),
      };

      // 设置 metadata.name（如果还没有的话）
      if (!params.metadata.name) {
        params.metadata.name = randomUUID();
      }

      console.log(`[HaloService] 开始发布文章，站点: ${this.site.url}`);
      console.log(`[HaloService] 文章标题: ${params.spec.title}`);
      console.log(`[HaloService] 文章 slug: ${params.spec.slug}`);
      console.log(`[HaloService] 文章 name: ${params.metadata.name}`);

      // 发送创建/更新请求
      const isUpdate = !!matterData?.halo?.name;
      
      if (isUpdate) {
        console.log(`[HaloService] 更新现有文章: ${params.metadata.name}`);
        
        await requestUrl({
          url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${params.metadata.name}`,
          method: "PUT",
          contentType: "application/json",
          headers: this.headers,
          body: JSON.stringify(params),
        });
        
        console.log(`[HaloService] 文章基本信息更新成功`);
      } else {
        console.log(`[HaloService] 创建新文章`);
        
        const post = await requestUrl({
          url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts`,
          method: "POST",
          contentType: "application/json",
          headers: this.headers,
          body: JSON.stringify(params),
        }).json;
        
        console.log(`[HaloService] 文章创建响应:`, JSON.stringify(post));
        
        if (!post || !post.metadata) {
          console.error(`[HaloService] 创建文章响应格式错误:`, post);
          throw new Error("创建文章响应格式错误");
        }
        
        console.log(`[HaloService] 文章创建成功: ${post.metadata.name}`);
        params = post;
      }

      // 处理发布状态
      // biome-ignore lint: no
      if (matterData?.halo?.hasOwnProperty("publish")) {
        if (matterData?.halo?.publish) {
          await this.changePostPublish(params.metadata.name, true);
        } else {
          await this.changePostPublish(params.metadata.name, false);
        }
      } else {
        if (this.settings.publishByDefault) {
          await this.changePostPublish(params.metadata.name, true);
        }
      }

      const postResult = await this.getPost(params.metadata.name);
      
      if (!postResult || !postResult.post.metadata) {
        console.error("[HaloService] 获取文章详情失败");
        new Notice(i18next.t("service.error_publish_failed"));
        return;
      }

      params = postResult.post;

      const postCategories = await this.getCategoryDisplayNames(params.spec.categories);
      const postTags = await this.getTagDisplayNames(params.spec.tags);

      this.app.fileManager.processFrontMatter(activeEditor.file, (frontmatter) => {
        frontmatter.title = params.spec.title;
        frontmatter.slug = params.spec.slug;
        frontmatter.cover = params.spec.cover;
        frontmatter.excerpt = params.spec.excerpt.autoGenerate ? undefined : params.spec.excerpt.raw;
        frontmatter.categories = postCategories;
        frontmatter.tags = postTags;
        frontmatter.halo = {
          site: this.site.url,
          name: params.metadata.name,
          publish: params.spec.publish,
        };
      });

      new Notice(i18next.t("service.notice_publish_success"));
    } catch (error) {
      console.error(`[HaloService] 发布失败，错误:`, error);
      new Notice(i18next.t("service.error_publish_failed"));
      return;
    }
  }

  public async changePostPublish(name: string, publish: boolean): Promise<void> {
    await requestUrl({
      url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}/${publish ? "publish" : "unpublish"}`,
      method: "PUT",
      contentType: "application/json",
      headers: this.headers,
    });
  }

  public async getCategories(): Promise<Category[]> {
    const data = await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/categories`,
      headers: this.headers,
    });
    return Promise.resolve(data.json.items);
  }

  public async getTags(): Promise<Tag[]> {
    const data = await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/tags`,
      headers: this.headers,
    });
    return Promise.resolve(data.json.items);
  }

  public async updatePost(): Promise<void> {
    const { activeEditor } = this.app.workspace;

    if (!activeEditor || !activeEditor.file) {
      return;
    }

    const matterData = this.app.metadataCache.getFileCache(activeEditor.file)?.frontmatter;

    if (!matterData?.halo?.name) {
      new Notice(i18next.t("service.error_not_published"));
      return;
    }

    const post = await this.getPost(matterData.halo.name);

    if (!post) {
      new Notice(i18next.t("service.error_post_not_found"));
      return;
    }

    const postCategories = await this.getCategoryDisplayNames(post.post.spec.categories);
    const postTags = await this.getTagDisplayNames(post.post.spec.tags);

    await this.app.vault.modify(activeEditor.file, `${post.content.raw}`);

    this.app.fileManager.processFrontMatter(activeEditor.file, (frontmatter) => {
      frontmatter.title = post.post.spec.title;
      frontmatter.slug = post.post.spec.slug;
      frontmatter.cover = post.post.spec.cover;
      frontmatter.excerpt = post.post.spec.excerpt.autoGenerate ? undefined : post.post.spec.excerpt.raw;
      frontmatter.categories = postCategories;
      frontmatter.tags = postTags;
      frontmatter.halo = {
        site: this.site.url,
        name: post.post.metadata.name,
        publish: post.post.spec.publish,
      };
    });
  }

  public async pullPost(name: string): Promise<void> {
    const post = await this.getPost(name);

    if (!post) {
      new Notice(i18next.t("service.error_post_not_found"));
      return;
    }

    const postCategories = await this.getCategoryDisplayNames(post.post.spec.categories);
    const postTags = await this.getTagDisplayNames(post.post.spec.tags);

    const file = await this.app.vault.create(`${post.post.spec.title}.md`, `${post.content.raw}`);
    this.app.workspace.getLeaf().openFile(file);

    this.app.fileManager.processFrontMatter(file, (frontmatter) => {
      frontmatter.title = post.post.spec.title;
      frontmatter.slug = post.post.spec.slug;
      frontmatter.cover = post.post.spec.cover;
      frontmatter.excerpt = post.post.spec.excerpt.autoGenerate ? undefined : post.post.spec.excerpt.raw;
      frontmatter.categories = postCategories;
      frontmatter.tags = postTags;
      frontmatter.halo = {
        site: this.site.url,
        name: name,
        publish: post.post.spec.publish,
      };
    });
  }

  public async getCategoryNames(displayNames: string[]): Promise<string[]> {
    const allCategories = await this.getCategories();

    const notExistDisplayNames = displayNames.filter(
      (name) => !allCategories.find((item) => item.spec.displayName === name),
    );

    const promises = notExistDisplayNames.map((name, index) =>
      requestUrl({
        url: `${this.site.url}/apis/content.halo.run/v1alpha1/categories`,
        method: "POST",
        contentType: "application/json",
        headers: this.headers,
        body: JSON.stringify({
          spec: {
            displayName: name,
            slug: slugify(name, { trim: true }),
            description: "",
            cover: "",
            template: "",
            priority: allCategories.length + index,
            children: [],
          },
          apiVersion: "content.halo.run/v1alpha1",
          kind: "Category",
          metadata: { name: "", generateName: "category-" },
        }),
      }),
    );

    const newCategories = await Promise.all(promises);

    const existNames = displayNames
      .map((name) => {
        const found = allCategories.find((item) => item.spec.displayName === name);
        return found ? found.metadata.name : undefined;
      })
      .filter(Boolean) as string[];

    return [...existNames, ...newCategories.map((item) => item.json.metadata.name)];
  }

  public async getCategoryDisplayNames(names?: string[]): Promise<string[]> {
    const categories = await this.getCategories();
    return names
      ?.map((name) => {
        const found = categories.find((item) => item.metadata.name === name);
        return found ? found.spec.displayName : undefined;
      })
      .filter(Boolean) as string[];
  }

  public async getTagNames(displayNames: string[]): Promise<string[]> {
    const allTags = await this.getTags();

    const notExistDisplayNames = displayNames.filter((name) => !allTags.find((item) => item.spec.displayName === name));

    const promises = notExistDisplayNames.map((name) =>
      requestUrl({
        url: `${this.site.url}/apis/content.halo.run/v1alpha1/tags`,
        method: "POST",
        contentType: "application/json",
        headers: this.headers,
        body: JSON.stringify({
          spec: {
            displayName: name,
            slug: slugify(name, { trim: true }),
            color: "#ffffff",
            cover: "",
          },
          apiVersion: "content.halo.run/v1alpha1",
          kind: "Tag",
          metadata: { name: "", generateName: "tag-" },
        }),
      }),
    );

    const newTags = await Promise.all(promises);

    const existNames = displayNames
      .map((name) => {
        const found = allTags.find((item) => item.spec.displayName === name);
        return found ? found.metadata.name : undefined;
      })
      .filter(Boolean) as string[];

    return [...existNames, ...newTags.map((item) => item.json.metadata.name)];
  }

  public async getTagDisplayNames(names?: string[]): Promise<string[]> {
    const tags = await this.getTags();
    return names
      ?.map((name) => {
        const found = tags.find((item) => item.metadata.name === name);
        return found ? found.spec.displayName : undefined;
      })
      .filter(Boolean) as string[];
  }

  public async importPost(file: TFile, publishImmediately: boolean = false): Promise<boolean> {
    try {
      const imageUploader = new ImageUploader(this.site.url, this.site.token);
      
      const md = await this.app.vault.read(file);
      const matterData = this.app.metadataCache.getFileCache(file)?.frontmatter;
      const frontmatterPosition = this.app.metadataCache.getFileCache(file)?.frontmatterPosition;
      
      let raw = frontmatterPosition ? md.slice(frontmatterPosition.end.offset) : md;
      
      // 检测并上传本地图片
      if (this.settings.imageUpload?.enabled) {
        const imageReferences = extractImageReferences(raw);
        if (imageReferences.length > 0) {
          const localImages = imageReferences.filter((ref) => 
            !ref.path.startsWith("http://") && 
            !ref.path.startsWith("https://") && 
            !ref.path.startsWith("data:")
          );
          
          if (localImages.length > 0) {
            new Notice(`检测到 ${localImages.length} 个本地图片，正在上传...`);
            
            const absolutePaths = localImages
              .map((ref) => ({
                original: ref.path,
                absolute: getAbsolutePath(this.app.vault, ref.path, file.path),
              }))
              .filter((item) => item.absolute !== null) as { original: string; absolute: string }[];
            
            if (absolutePaths.length > 0) {
              const pathMapping = await imageUploader.uploadImages(absolutePaths.map((item) => item.absolute), this.app.vault);
              
              if (pathMapping.size > 0) {
                const mapping = new Map<string, string>();
                absolutePaths.forEach((item) => {
                  const remoteUrl = pathMapping.get(item.absolute);
                  if (remoteUrl) {
                    mapping.set(item.original, remoteUrl);
                  }
                });
                
                raw = replaceImagePaths(raw, mapping);
                
                const successCount = mapping.size;
                const failCount = absolutePaths.length - successCount;
                
                if (failCount === 0) {
                  new Notice(`✓ 图片上传成功 (${successCount}/${absolutePaths.length})`);
                } else {
                  new Notice(`⚠ 部分图片上传失败 (成功: ${successCount}, 失败: ${failCount})`);
                }
              }
            }
          }
        }
      }
      
      const content: Content = {
        rawType: "markdown",
        raw: raw,
        content: markdownIt.render(raw),
      };
      
      let params: Post = {
        apiVersion: "content.halo.run/v1alpha1",
        kind: "Post",
        metadata: {
          annotations: {
            "content.halo.run/content-json": JSON.stringify(content),
          },
          name: randomUUID(),
        },
        spec: {
          allowComment: true,
          baseSnapshot: "",
          categories: [],
          cover: matterData?.cover || "",
          deleted: false,
          excerpt: {
            autoGenerate: matterData?.excerpt ? false : true,
            raw: matterData?.excerpt || "",
          },
          headSnapshot: "",
          htmlMetas: [],
          owner: "",
          pinned: false,
          priority: 0,
          publish: publishImmediately,
          publishTime: "",
          releaseSnapshot: "",
          slug: matterData?.slug || slugify(matterData?.title || file.basename, { trim: true }),
          tags: [],
          template: "",
          title: matterData?.title || file.basename,
          visible: "PUBLIC",
        },
      };
      
      // 处理分类
      if (matterData?.categories) {
        const categoryNames = await this.getCategoryNames(matterData.categories);
        params.spec.categories = categoryNames;
      }
      
      // 处理标签
      if (matterData?.tags) {
        const tagNames = await this.getTagNames(matterData.tags);
        params.spec.tags = tagNames;
      }
      
      // 创建文章
      const post = await requestUrl({
        url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts`,
        method: "POST",
        contentType: "application/json",
        headers: this.headers,
        body: JSON.stringify(params),
      }).json;
      
      return !!post && !!post.metadata;
    } catch (error) {
      console.error("[HaloService] 导入文章失败:", error);
      return false;
    }
  }

  public async deletePost(name: string): Promise<boolean> {
    try {
      await requestUrl({
        url: `${this.site.url}/apis/uc.api.content.halo.run/v1alpha1/posts/${name}`,
        method: "DELETE",
        headers: this.headers,
      });
      
      return true;
    } catch (error) {
      console.error("[HaloService] 删除文章失败:", error);
      return false;
    }
  }

  public async createTag(displayName: string, slug: string, color: string): Promise<void> {
    await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/tags`,
      method: "POST",
      contentType: "application/json",
      headers: this.headers,
      body: JSON.stringify({
        spec: {
          displayName,
          slug,
          color: color || "#ffffff",
          cover: "",
        },
        apiVersion: "content.halo.run/v1alpha1",
        kind: "Tag",
        metadata: { name: "", generateName: "tag-" },
      }),
    });
  }

  public async updateTag(name: string, displayName: string, slug: string, color: string): Promise<void> {
    const tag = (await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/tags/${name}`,
      headers: this.headers,
    }).json) as any;

    tag.spec.displayName = displayName;
    tag.spec.slug = slug;
    tag.spec.color = color || "#ffffff";

    await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/tags/${name}`,
      method: "PUT",
      contentType: "application/json",
      headers: this.headers,
      body: JSON.stringify(tag),
    });
  }

  public async deleteTag(name: string): Promise<void> {
    await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/tags/${name}`,
      method: "DELETE",
      headers: this.headers,
    });
  }

  public async createCategory(displayName: string, slug: string, priority: number): Promise<void> {
    await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/categories`,
      method: "POST",
      contentType: "application/json",
      headers: this.headers,
      body: JSON.stringify({
        spec: {
          displayName,
          slug,
          description: "",
          cover: "",
          template: "",
          priority,
          children: [],
        },
        apiVersion: "content.halo.run/v1alpha1",
        kind: "Category",
        metadata: { name: "", generateName: "category-" },
      }),
    });
  }

  public async updateCategory(name: string, displayName: string, slug: string, priority: number): Promise<void> {
    const category = (await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/categories/${name}`,
      headers: this.headers,
    }).json) as any;

    category.spec.displayName = displayName;
    category.spec.slug = slug;
    category.spec.priority = priority;

    await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/categories/${name}`,
      method: "PUT",
      contentType: "application/json",
      headers: this.headers,
      body: JSON.stringify(category),
    });
  }

  public async deleteCategory(name: string): Promise<void> {
    await requestUrl({
      url: `${this.site.url}/apis/content.halo.run/v1alpha1/categories/${name}`,
      method: "DELETE",
      headers: this.headers,
    });
  }
}

export default HaloService;
