import axios, { AxiosInstance } from 'axios';
import type { JiraConfig } from '../types';

export class JiraProvider {
  private readonly config: JiraConfig;
  private readonly client: AxiosInstance;

  constructor(config: JiraConfig) {
    this.config = config;

    const credentials = Buffer.from(
      `${config.user}:${config.api_token}`
    ).toString('base64');

    this.client = axios.create({
      baseURL: config.base_url.replace(/\/$/, ''),
      headers: {
        Authorization: `Basic ${credentials}`,
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
    });
  }

  // ── Issues ────────────────────────────────────────────────────────────────

  async createCard(title: string, description: string): Promise<string> {
    const payload = {
      fields: {
        project: { key: this.config.project_key },
        summary: title,
        description: this._toAdf(description),
        issuetype: { name: 'Story' },
      },
    };

    try {
      const response = await this.client.post<{ key: string }>(
        '/rest/api/3/issue',
        payload
      );

      const cardId = response.data?.key;
      if (!cardId) {
        throw new Error('API did not return issue key');
      }
      return cardId;
    } catch (err: unknown) {
      const msg = this._extractMessage(err);
      throw new Error(
        `JiraProvider.createCard: failed to create issue "${title}" in project "${this.config.project_key}": ${msg}`
      );
    }
  }

  async addComment(cardId: string, text: string): Promise<void> {
    const payload = {
      body: this._toAdf(text),
    };

    try {
      await this.client.post(
        `/rest/api/3/issue/${encodeURIComponent(cardId)}/comment`,
        payload
      );
    } catch (err: unknown) {
      const msg = this._extractMessage(err);
      throw new Error(
        `JiraProvider.addComment: failed to add comment to "${cardId}": ${msg}`
      );
    }
  }

  async getLastComment(cardId: string): Promise<string | null> {
    try {
      const response = await this.client.get<{
        comments: Array<{
          body?: {
            content?: Array<{
              content?: Array<{ text?: string }>;
            }>;
          };
        }>;
        total: number;
      }>(`/rest/api/3/issue/${encodeURIComponent(cardId)}/comment`, {
        params: { orderBy: '-created', maxResults: 1 },
      });

      const comments = response.data?.comments;
      if (!comments || comments.length === 0) return null;

      return this._adfToPlainText(comments[0].body) ?? null;
    } catch (err: unknown) {
      const msg = this._extractMessage(err);
      throw new Error(
        `JiraProvider.getLastComment: failed to fetch comments for "${cardId}": ${msg}`
      );
    }
  }

  async updateDescription(cardId: string, description: string): Promise<void> {
    const payload = {
      fields: {
        description: this._toAdf(description),
      },
    };

    try {
      await this.client.put(
        `/rest/api/3/issue/${encodeURIComponent(cardId)}`,
        payload
      );
    } catch (err: unknown) {
      const msg = this._extractMessage(err);
      throw new Error(
        `JiraProvider.updateDescription: failed to update description for "${cardId}": ${msg}`
      );
    }
  }

  // ── ADF helpers ───────────────────────────────────────────────────────────

  /**
   * Converts a plain-text / Markdown string to the minimal Atlassian Document
   * Format (ADF) structure accepted by Jira REST API v3.
   * Each newline-separated paragraph is wrapped in a paragraph node.
   */
  private _toAdf(text: string): object {
    const paragraphs = text.split('\n').map((line) => ({
      type: 'paragraph',
      content: [
        {
          type: 'text',
          text: line,
        },
      ],
    }));

    return {
      version: 1,
      type: 'doc',
      content: paragraphs.length > 0 ? paragraphs : [
        { type: 'paragraph', content: [{ type: 'text', text: '' }] },
      ],
    };
  }

  /**
   * Extracts plain text from an ADF body node.
   * Falls back to an empty string when the structure is unrecognised.
   */
  private _adfToPlainText(
    body?: {
      content?: Array<{
        content?: Array<{ text?: string }>;
      }>;
    }
  ): string {
    if (!body?.content) return '';
    return body.content
      .flatMap((block) => block.content ?? [])
      .map((inline) => inline.text ?? '')
      .join('\n');
  }

  // ── Error helpers ─────────────────────────────────────────────────────────

  private _extractMessage(err: unknown): string {
    if (axios.isAxiosError(err)) {
      const data = err.response?.data as
        | { errorMessages?: string[]; errors?: Record<string, string> }
        | undefined;
      if (data) {
        const parts: string[] = [];
        if (data.errorMessages?.length) parts.push(data.errorMessages.join(', '));
        if (data.errors && Object.keys(data.errors).length) {
          parts.push(
            Object.entries(data.errors)
              .map(([k, v]) => `${k}: ${v}`)
              .join(', ')
          );
        }
        if (parts.length) return parts.join(' | ');
        return `HTTP ${err.response?.status ?? 'unknown'}`;
      }
      return err.message;
    }
    return (err as Error).message ?? JSON.stringify(err);
  }
}
