import { nanoid } from "nanoid";

import type {
  ConfirmedFactCard,
  ImageInput,
  OptimizedArticle,
  PublishPlatform,
  QaReport,
} from "../domain/types";
import type { AppRepository } from "./repository";
import type { ArticleJob, BrandTemplate } from "./repositories";

type JsonObject = Record<string, unknown>;

interface BrandTemplateRow {
  id: string;
  brand_name: string;
  company_full_name: string;
  company_short_names: string;
  product_names: string;
  target_industries: string;
  target_audience: string;
  verified_claims: string;
  forbidden_claims: string;
  tone_rules: string;
  created_at: string;
  updated_at: string;
}

interface ArticleJobRow {
  id: string;
  brand_template_id: string | null;
  source_title: string;
  source_body: string;
  image_inputs: string;
  publish_platform: PublishPlatform;
  user_instructions: string;
  status: string;
  export_paths: string;
  created_at: string;
  updated_at: string;
}

interface FactCardRow {
  job_id: string;
  fact_card: string;
}

interface OptimizedArticleRow {
  job_id: string;
  revision: number;
  article: string;
}

interface QaReportRow {
  job_id: string;
  revision: number;
  report: string;
}

function nowIso() {
  return new Date().toISOString();
}

function serialize(value: unknown) {
  return JSON.stringify(value);
}

function parseJson<T>(value: string): T {
  return JSON.parse(value) as T;
}

function toBrandTemplate(row: BrandTemplateRow): BrandTemplate {
  return {
    ...row,
    company_short_names: parseJson<string[]>(row.company_short_names),
    product_names: parseJson<string[]>(row.product_names),
    target_industries: parseJson<string[]>(row.target_industries),
    target_audience: parseJson<string[]>(row.target_audience),
    verified_claims: parseJson<string[]>(row.verified_claims),
    forbidden_claims: parseJson<string[]>(row.forbidden_claims),
    tone_rules: parseJson<JsonObject>(row.tone_rules),
  };
}

function toArticleJob(row: ArticleJobRow): ArticleJob {
  return {
    ...row,
    image_inputs: parseJson<ImageInput[]>(row.image_inputs),
    export_paths: parseJson<Record<string, string>>(row.export_paths),
  };
}

export function createD1Repository(db: D1Database): AppRepository {
  return {
    async createBrandTemplate(input) {
      const createdAt = nowIso();
      const template: BrandTemplate = {
        id: `brand_${nanoid(10)}`,
        ...input,
        created_at: createdAt,
        updated_at: createdAt,
      };

      await db
        .prepare(
          `insert into brand_templates (
            id, brand_name, company_full_name, company_short_names, product_names,
            target_industries, target_audience, verified_claims, forbidden_claims,
            tone_rules, created_at, updated_at
          ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
        )
        .bind(
          template.id,
          template.brand_name,
          template.company_full_name,
          serialize(template.company_short_names),
          serialize(template.product_names),
          serialize(template.target_industries),
          serialize(template.target_audience),
          serialize(template.verified_claims),
          serialize(template.forbidden_claims),
          serialize(template.tone_rules),
          template.created_at,
          template.updated_at,
        )
        .run();

      return template;
    },
    async listBrandTemplates() {
      const result = await db
        .prepare("select * from brand_templates order by updated_at desc")
        .all<BrandTemplateRow>();
      return result.results.map(toBrandTemplate);
    },
    async getBrandTemplate(id) {
      const row = await db
        .prepare("select * from brand_templates where id = ?")
        .bind(id)
        .first<BrandTemplateRow>();
      return row ? toBrandTemplate(row) : null;
    },
    async createArticleJob(input) {
      const createdAt = nowIso();
      const job: ArticleJob = {
        id: `job_${nanoid(10)}`,
        brand_template_id: input.brand_template_id ?? null,
        source_title: input.source_title,
        source_body: input.source_body,
        image_inputs: input.image_inputs,
        publish_platform: input.publish_platform,
        user_instructions: input.user_instructions,
        status: "draft",
        export_paths: {},
        created_at: createdAt,
        updated_at: createdAt,
      };

      await db
        .prepare(
          `insert into article_jobs (
            id, brand_template_id, source_title, source_body, image_inputs,
            publish_platform, user_instructions, status, export_paths, created_at, updated_at
          ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
        )
        .bind(
          job.id,
          job.brand_template_id,
          job.source_title,
          job.source_body,
          serialize(job.image_inputs),
          job.publish_platform,
          job.user_instructions,
          job.status,
          serialize(job.export_paths),
          job.created_at,
          job.updated_at,
        )
        .run();

      return job;
    },
    async getArticleJob(id) {
      const row = await db
        .prepare("select * from article_jobs where id = ?")
        .bind(id)
        .first<ArticleJobRow>();
      return row ? toArticleJob(row) : null;
    },
    async updateArticleJob(id, changes) {
      const existing = await this.getArticleJob(id);
      if (!existing) return null;
      const updated = {
        brand_template_id: changes.brand_template_id ?? existing.brand_template_id,
        status: changes.status ?? existing.status,
        export_paths: changes.export_paths ?? existing.export_paths,
        updated_at: nowIso(),
      };
      await db
        .prepare(
          `update article_jobs set
            brand_template_id = ?,
            status = ?,
            export_paths = ?,
            updated_at = ?
           where id = ?`,
        )
        .bind(
          updated.brand_template_id,
          updated.status,
          serialize(updated.export_paths),
          updated.updated_at,
          id,
        )
        .run();
      return this.getArticleJob(id);
    },
    async saveFactCard(jobId, factCard) {
      const timestamp = nowIso();
      await db
        .prepare(
          `insert into fact_cards (
            job_id, source, fact_card, confirmed_by_user, created_at, updated_at
          ) values (?, ?, ?, ?, ?, ?)
          on conflict(job_id) do update set
            fact_card = excluded.fact_card,
            confirmed_by_user = excluded.confirmed_by_user,
            updated_at = excluded.updated_at`,
        )
        .bind(
          jobId,
          "auto_extract_then_user_confirmed",
          serialize(factCard),
          factCard.confirmed_by_user ? 1 : 0,
          timestamp,
          timestamp,
        )
        .run();
      return { job_id: jobId, ...factCard };
    },
    async getFactCard(jobId) {
      const row = await db
        .prepare("select job_id, fact_card from fact_cards where job_id = ?")
        .bind(jobId)
        .first<FactCardRow>();
      return row
        ? { job_id: row.job_id, ...parseJson<ConfirmedFactCard>(row.fact_card) }
        : null;
    },
    async saveOptimizedArticle(jobId, article) {
      const latest = await this.getLatestOptimizedArticle(jobId);
      const revision = (latest?.revision ?? 0) + 1;
      const saved = { ...article, job_id: jobId, revision };
      await db
        .prepare(
          `insert into optimized_articles (job_id, revision, article, created_at)
           values (?, ?, ?, ?)`,
        )
        .bind(jobId, revision, serialize(saved), nowIso())
        .run();
      return saved;
    },
    async getLatestOptimizedArticle(jobId) {
      const row = await db
        .prepare(
          `select job_id, revision, article
           from optimized_articles
           where job_id = ?
           order by revision desc
           limit 1`,
        )
        .bind(jobId)
        .first<OptimizedArticleRow>();
      return row ? parseJson<OptimizedArticle>(row.article) : null;
    },
    async saveQaReport(jobId, revision, report) {
      const saved = { ...report, job_id: jobId, revision };
      await db
        .prepare(
          `insert into qa_reports (job_id, revision, report, created_at)
           values (?, ?, ?, ?)
           on conflict(job_id, revision) do update set report = excluded.report`,
        )
        .bind(jobId, revision, serialize(saved), nowIso())
        .run();
      return saved;
    },
    async getLatestQaReport(jobId) {
      const row = await db
        .prepare(
          `select job_id, revision, report
           from qa_reports
           where job_id = ?
           order by revision desc
           limit 1`,
        )
        .bind(jobId)
        .first<QaReportRow>();
      return row ? parseJson<QaReport>(row.report) : null;
    },
  };
}
