import { nanoid } from "nanoid";

import type {
  ConfirmedFactCard,
  ImageInput,
  OptimizedArticle,
  PublishPlatform,
  QaReport,
} from "../domain/types";

import { createDatabase, getDefaultDatabasePath } from "./connection";
import { initializeSchema } from "./schema";

type JsonObject = Record<string, unknown>;

export interface BrandTemplate {
  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: JsonObject;
  created_at: string;
  updated_at: string;
}

export type NewBrandTemplate = Omit<
  BrandTemplate,
  "id" | "created_at" | "updated_at"
>;

export interface ArticleJob {
  id: string;
  brand_template_id: string | null;
  source_title: string;
  source_body: string;
  image_inputs: ImageInput[];
  publish_platform: PublishPlatform;
  user_instructions: string;
  status: string;
  export_paths: Record<string, string>;
  created_at: string;
  updated_at: string;
}

export interface NewArticleJob {
  brand_template_id?: string | null;
  source_title: string;
  source_body: string;
  image_inputs: ImageInput[];
  publish_platform: PublishPlatform;
  user_instructions: string;
}

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 withDb<T>(dbPath: string | undefined, action: (db: ReturnType<typeof createDatabase>) => T) {
  const db = createDatabase(dbPath ?? getDefaultDatabasePath());
  initializeSchema(db);
  try {
    return action(db);
  } finally {
    db.close();
  }
}

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 createBrandTemplate(
  dbPath: string | undefined,
  input: NewBrandTemplate,
) {
  return withDb(dbPath, (db) => {
    const createdAt = nowIso();
    const template: BrandTemplate = {
      id: `brand_${nanoid(10)}`,
      ...input,
      created_at: createdAt,
      updated_at: createdAt,
    };

    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 (
        @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
      )`,
    ).run({
      ...template,
      company_short_names: serialize(template.company_short_names),
      product_names: serialize(template.product_names),
      target_industries: serialize(template.target_industries),
      target_audience: serialize(template.target_audience),
      verified_claims: serialize(template.verified_claims),
      forbidden_claims: serialize(template.forbidden_claims),
      tone_rules: serialize(template.tone_rules),
    });

    return template;
  });
}

export function listBrandTemplates(dbPath?: string) {
  return withDb(dbPath, (db) =>
    db
      .prepare("select * from brand_templates order by updated_at desc")
      .all()
      .map((row) => toBrandTemplate(row as BrandTemplateRow)),
  );
}

export function getBrandTemplate(dbPath: string | undefined, id: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select * from brand_templates where id = ?")
      .get(id) as BrandTemplateRow | undefined;
    return row ? toBrandTemplate(row) : null;
  });
}

export function createArticleJob(dbPath: string | undefined, input: NewArticleJob) {
  return withDb(dbPath, (db) => {
    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,
    };

    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 (
        @id, @brand_template_id, @source_title, @source_body, @image_inputs,
        @publish_platform, @user_instructions, @status, @export_paths, @created_at, @updated_at
      )`,
    ).run({
      ...job,
      image_inputs: serialize(job.image_inputs),
      export_paths: serialize(job.export_paths),
    });

    return job;
  });
}

export function getArticleJob(dbPath: string | undefined, id: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select * from article_jobs where id = ?")
      .get(id) as ArticleJobRow | undefined;
    return row ? toArticleJob(row) : null;
  });
}

export function updateArticleJob(
  dbPath: string | undefined,
  id: string,
  changes: Partial<Pick<ArticleJob, "brand_template_id" | "status" | "export_paths">>,
) {
  return withDb(dbPath, (db) => {
    const existing = getArticleJob(dbPath, 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(),
    };
    db.prepare(
      `update article_jobs set
        brand_template_id = @brand_template_id,
        status = @status,
        export_paths = @export_paths,
        updated_at = @updated_at
       where id = @id`,
    ).run({
      id,
      ...updated,
      export_paths: serialize(updated.export_paths),
    });
    return getArticleJob(dbPath, id);
  });
}

export function saveFactCard(
  dbPath: string | undefined,
  jobId: string,
  factCard: ConfirmedFactCard,
) {
  return withDb(dbPath, (db) => {
    const timestamp = nowIso();
    db.prepare(
      `insert into fact_cards (
        job_id, source, fact_card, confirmed_by_user, created_at, updated_at
      ) values (
        @job_id, @source, @fact_card, @confirmed_by_user, @created_at, @updated_at
      )
      on conflict(job_id) do update set
        fact_card = excluded.fact_card,
        confirmed_by_user = excluded.confirmed_by_user,
        updated_at = excluded.updated_at`,
    ).run({
      job_id: jobId,
      source: "auto_extract_then_user_confirmed",
      fact_card: serialize(factCard),
      confirmed_by_user: factCard.confirmed_by_user ? 1 : 0,
      created_at: timestamp,
      updated_at: timestamp,
    });

    return { job_id: jobId, ...factCard };
  });
}

export function getFactCard(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare("select job_id, fact_card from fact_cards where job_id = ?")
      .get(jobId) as FactCardRow | undefined;
    return row
      ? { job_id: row.job_id, ...parseJson<ConfirmedFactCard>(row.fact_card) }
      : null;
  });
}

export function saveOptimizedArticle(
  dbPath: string | undefined,
  jobId: string,
  article: OptimizedArticle,
) {
  return withDb(dbPath, (db) => {
    const nextRevision =
      ((db
        .prepare(
          "select max(revision) as revision from optimized_articles where job_id = ?",
        )
        .get(jobId) as { revision: number | null }).revision ?? 0) + 1;
    const saved = { ...article, job_id: jobId, revision: nextRevision };

    db.prepare(
      `insert into optimized_articles (job_id, revision, article, created_at)
       values (?, ?, ?, ?)`,
    ).run(jobId, nextRevision, serialize(saved), nowIso());

    return saved;
  });
}

export function getLatestOptimizedArticle(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select job_id, revision, article from optimized_articles
         where job_id = ? order by revision desc limit 1`,
      )
      .get(jobId) as OptimizedArticleRow | undefined;
    return row ? parseJson<OptimizedArticle>(row.article) : null;
  });
}

export function saveQaReport(
  dbPath: string | undefined,
  jobId: string,
  revision: number,
  report: QaReport,
) {
  return withDb(dbPath, (db) => {
    const saved = { ...report, job_id: jobId, revision };
    db.prepare(
      `insert into qa_reports (job_id, revision, report, created_at)
       values (?, ?, ?, ?)
       on conflict(job_id, revision) do update set
        report = excluded.report`,
    ).run(jobId, revision, serialize(saved), nowIso());
    return saved;
  });
}

export function getLatestQaReport(dbPath: string | undefined, jobId: string) {
  return withDb(dbPath, (db) => {
    const row = db
      .prepare(
        `select job_id, revision, report from qa_reports
         where job_id = ? order by revision desc limit 1`,
      )
      .get(jobId) as QaReportRow | undefined;
    return row ? parseJson<QaReport>(row.report) : null;
  });
}
