/**
 * WebDAV 同步（plan.md「6.3 WebDAV 同步」）。
 *
 * 参考 BeeCount + AutoAccounting 的 WebDAVManager：
 * - 上传/下载 main.bean
 * - 冲突检测（对比 lastModified）
 * - 备份管理（保留最近 N 个）
 *
 * HTTP 请求通过注入的 fetch 抽象（生产用 RN fetch，测试用 mock），
 * WebDAV 协议通过 PROPFIND/GET/PUT/DELETE 实现。
 */

import { logger } from '../../utils/logger';
import type { SyncConflict } from '../../domain/platform/sync';

export interface WebDAVConfig {
  url: string;            // 如 https://dav.example.com/beancount/
  username: string;
  password: string;
  remotePath: string;     // 如 /main.bean
  backupKeepCount: number;  // 备份保留数，默认 10
}

export interface WebDAVSyncResult {
  action: 'pushed' | 'pulled' | 'conflict' | 'noop';
  content: string;
  conflict?: SyncConflict;
}

/** fetch 抽象（生产用全局 fetch，测试用 mock）。 */
export type FetchImpl = (url: string, init: WebDAVRequestInit) => Promise<WebDAVResponse>;

export interface WebDAVRequestInit {
  method: string;
  headers: Record<string, string>;
  body?: string;
}

export interface WebDAVResponse {
  ok: boolean;
  status: number;
  text(): Promise<string>;
  /** 返回头（Last-Modified 等）。 */
  headers: { get(name: string): string | null };
}

/**
 * WebDAV 客户端。
 */
export class WebDAVClient {
  private authHeader: string;

  constructor(
    private readonly config: WebDAVConfig,
    private readonly fetchImpl: FetchImpl,
  ) {
    this.authHeader = 'Basic ' + encodeBase64(`${config.username}:${config.password}`);
  }

  private buildUrl(path: string): string {
    const url = this.config.url.endsWith('/') ? this.config.url.slice(0, -1) : this.config.url;
    const p = path.startsWith('/') ? path : '/' + path;
    return url + p;
  }

  /** 获取远程文件信息（Last-Modified / 大小）。 */
  async stat(): Promise<{ lastModified: string | null; exists: boolean }> {
    const url = this.buildUrl(this.config.remotePath);
    try {
      const resp = await this.fetchImpl(url, {
        method: 'PROPFIND',
        headers: {
          'Authorization': this.authHeader,
          'Depth': '0',
          'Content-Type': 'application/xml',
        },
        body: '<?xml version="1.0"?><propfind xmlns="DAV:"><prop><getlastmodified/></prop></propfind>',
      });
      if (!resp.ok) {
        logger.warn('webdavSync', `PROPFIND 请求未成功 [HTTP ${resp.status}], 目标路径: ${this.config.remotePath}`);
        return { lastModified: null, exists: false };
      }
      const body = await resp.text();
      const match = body.match(/<getlastmodified>([^<]+)<\/getlastmodified>/);
      const headerLastMod = resp.headers.get('Last-Modified');
      return { lastModified: match?.[1] ?? headerLastMod, exists: true };
    } catch (e) {
      logger.error('webdavSync', `PROPFIND 网络或数据异常 (URL: ${url}): ${e instanceof Error ? e.message : String(e)}`, e);
      return { lastModified: null, exists: false };
    }
  }

  /** 下载文件内容。 */
  async get(): Promise<string | null> {
    const url = this.buildUrl(this.config.remotePath);
    try {
      const resp = await this.fetchImpl(url, {
        method: 'GET',
        headers: { 'Authorization': this.authHeader },
      });
      if (!resp.ok) {
        logger.warn('webdavSync', `GET 远程文件未成功 [HTTP ${resp.status}], 目标路径: ${this.config.remotePath}`);
        return null;
      }
      return await resp.text();
    } catch (e) {
      logger.error('webdavSync', `GET 远程文件网络或数据异常 (URL: ${url}): ${e instanceof Error ? e.message : String(e)}`, e);
      return null;
    }
  }

  /** 上传文件内容。 */
  async put(content: string): Promise<void> {
    const url = this.buildUrl(this.config.remotePath);
    const resp = await this.fetchImpl(url, {
      method: 'PUT',
      headers: {
        'Authorization': this.authHeader,
        'Content-Type': 'text/plain',
      },
      body: content,
    });
    if (!resp.ok) {
      logger.error('webdavSync', `PUT 上传文件失败 [HTTP ${resp.status}], 目标路径: ${this.config.remotePath}`);
      throw new Error(`WebDAV PUT 失败: ${resp.status}`);
    }
  }

  /** 列出备份目录。 */
  async listBackups(backupDir: string): Promise<string[]> {
    try {
      const resp = await this.fetchImpl(this.buildUrl(backupDir), {
        method: 'PROPFIND',
        headers: {
          'Authorization': this.authHeader,
          'Depth': '1',
          'Content-Type': 'application/xml',
        },
        body: '<?xml version="1.0"?><propfind xmlns="DAV:"><prop><displayname/></prop></propfind>',
      });
      if (!resp.ok) return [];
      const body = await resp.text();
      const names = [...body.matchAll(/<displayname>([^<]+)<\/displayname>/g)].map(m => m[1]);
      return names.filter(n => n.includes('backup'));
    } catch (e) {
      logger.warn('webdavSync', `列出备份目录失败 (路径: ${backupDir}): ${e}`);
      return [];
    }
  }

  /** 删除文件。 */
  async delete(path: string): Promise<void> {
    await this.fetchImpl(this.buildUrl(path), {
      method: 'DELETE',
      headers: { 'Authorization': this.authHeader },
    });
  }
}

/**
 * 执行 WebDAV 同步（快照式：全量上传/下载）。
 */
export async function syncWithWebDAV(
  client: WebDAVClient,
  localContent: string,
  lastSyncTime: string,
): Promise<WebDAVSyncResult> {
  const remoteInfo = await client.stat();
  const remoteContent = await client.get();

  // 无远程文件 → 推送
  if (remoteContent === null) {
    await client.put(localContent);
    logger.info('webdavSync', '首次推送');
    return { action: 'pushed', content: localContent };
  }

  // 内容相同 → noop
  if (remoteContent === localContent) {
    return { action: 'noop', content: localContent };
  }

  // 冲突检测：远程和本地都有变更
  // 将 HTTP 日期格式转为可比较的 ISO 字符串
  const parseHttpDate = (s: string): number => {
    const d = new Date(s);
    return isNaN(d.getTime()) ? 0 : d.getTime();
  };
  const remoteChanged = remoteInfo.lastModified &&
    parseHttpDate(remoteInfo.lastModified) > parseHttpDate(lastSyncTime);
  if (remoteChanged) {
    return {
      action: 'conflict',
      content: localContent,
      conflict: {
        entityType: 'main.bean',
        entityId: 'main.bean',
        local: localContent,
        remote: remoteContent,
        resolution: 'manual',
      },
    };
  }

  // 仅本地变更 → 推送
  await client.put(localContent);
  logger.info('webdavSync', '推送本地变更');
  return { action: 'pushed', content: localContent };
}

/**
 * 创建带时间戳的备份（参考 AutoAccounting WebDAVManager）。
 */
export async function backupToWebDAV(
  client: WebDAVClient,
  content: string,
  config: WebDAVConfig,
): Promise<string> {
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  const backupPath = `/backups/backup-${timestamp}.bean`;

  // 创建备份目录（MKCOL）
  try {
    await (client as unknown as { fetchImpl: FetchImpl; config: WebDAVConfig }).fetchImpl(
      config.url + '/backups/',
      { method: 'MKCOL', headers: { Authorization: (client as unknown as { authHeader: string }).authHeader } },
    );
  } catch { /* 目录可能已存在 */ }

  // 上传备份
  const backupClient = new WebDAVClient(
    { ...config, remotePath: backupPath },
    (client as unknown as { fetchImpl: FetchImpl }).fetchImpl,
  );
  await backupClient.put(content);

  // 清理旧备份
  const backups = await client.listBackups('/backups/');
  if (backups.length > config.backupKeepCount) {
    const sorted = backups.sort();
    for (const old of sorted.slice(0, sorted.length - config.backupKeepCount)) {
      try { await client.delete(`/backups/${old}`); } catch { /* 忽略删除失败 */ }
    }
  }

  return backupPath;
}

function encodeBase64(s: string): string {
  if (typeof btoa === 'function') return btoa(s);
  // Node 环境
  return Buffer.from(s, 'utf-8').toString('base64');
}

/** 模拟 fetch（测试用）。 */
export class MockFetch {
  public files = new Map<string, { content: string; lastModified: string }>();
  public requests: { url: string; method: string }[] = [];

  constructor() {}

  setFile(path: string, content: string, lastModified?: string): void {
    this.files.set(path, { content, lastModified: lastModified ?? new Date().toISOString() });
  }

  fetch: FetchImpl = async (url, init) => {
    this.requests.push({ url, method: init.method });
    const rawPath = new URL(url).pathname.replace(/\/+/g, '/');
    const file = this.files.get(rawPath)
      ?? this.files.get(rawPath.replace(/^\//, ''))
      ?? [...this.files.entries()].find(([k]) => rawPath.endsWith(k))?.[1];

    if (init.method === 'GET') {
      if (!file) return { ok: false, status: 404, text: async () => '', headers: { get: () => null } };
      return { ok: true, status: 200, text: async () => file.content, headers: { get: (n: string) => n === 'Last-Modified' ? file.lastModified : null } };
    }
    if (init.method === 'PUT') {
      this.files.set(rawPath, { content: init.body ?? '', lastModified: new Date().toISOString() });
      return { ok: true, status: 201, text: async () => '', headers: { get: () => null } };
    }
    if (init.method === 'PROPFIND') {
      if (!file) return { ok: false, status: 404, text: async () => '', headers: { get: () => null } };
      return { ok: true, status: 207, text: async () => `<x><getlastmodified>${file.lastModified}</getlastmodified></x>`, headers: { get: (n: string) => n === 'Last-Modified' ? file.lastModified : null } };
    }
    if (init.method === 'MKCOL') {
      return { ok: true, status: 201, text: async () => '', headers: { get: () => null } };
    }
    if (init.method === 'DELETE') {
      this.files.delete(rawPath);
      return { ok: true, status: 204, text: async () => '', headers: { get: () => null } };
    }
    return { ok: true, status: 200, text: async () => '', headers: { get: () => null } };
  };
}
