import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { randomUUID } from "node:crypto";

import { ContractRendererService } from "../../contracts/contract-renderer.service";
import { RateSchedule } from "../../contracts/contract-rate-schedule.builder";
import { getTemplateMeta } from "../../contracts/contract-template.registry";
import {
  ContractDynamicTemplateView,
  ContractViewModel,
} from "../../contracts/contract-view-model.builder";
import { ContractTemplatesRepository } from "./contract-templates.repository";
import {
  CreateArticleDto,
  PreviewContractTemplateDto,
  ReplaceArticleDto,
  UpdateArticleDto,
  UpdateContractTemplateDto,
} from "./dto/contract-template.dto";
import {
  CONTRACT_TEMPLATE_CODES,
  ContractTemplate,
  ContractTemplateArticle,
  ContractTemplateCode,
  contractTemplateCodeFor,
} from "./entities/contract-template.entity";

/** Registry keys used to derive labels for the mock preview per template code. */
const PREVIEW_TEMPLATE_KEYS: Record<ContractTemplateCode, string> = {
  IMPORT_BULK: "IMP_BULK_USD_FORWARDING",
  EXPORT_BULK: "EXP_BULK_USD_TRANSPORT_ONLY",
  INTERCITY_BULK: "DOM_BULK_USD_TRANSPORT_ONLY",
  IMPORT_CONTAINER: "IMP_CON_USD_TRANSPORT_ONLY",
  EXPORT_CONTAINER: "EXP_CON_USD_FORWARDING",
  INTERCITY_CONTAINER: "DOM_CON_USD_TRANSPORT_ONLY",
};

@Injectable()
export class ContractTemplatesService {
  constructor(
    private readonly repository: ContractTemplatesRepository,
    private readonly renderer: ContractRendererService,
  ) {}

  async list(): Promise<ContractTemplate[]> {
    const templates = await this.repository.findAll();
    const rank = new Map(CONTRACT_TEMPLATE_CODES.map((code, i) => [code, i] as const));
    return templates.sort(
      (a, b) => (rank.get(a.code) ?? 99) - (rank.get(b.code) ?? 99),
    );
  }

  async getByCode(code: string): Promise<ContractTemplate> {
    const template = await this.repository.findByCode(this.assertCode(code));
    if (!template) {
      throw new NotFoundException(`Contract template ${code} not found`);
    }
    return template;
  }

  /**
   * The active template used when generating a contract document for the given
   * direction/freight pair; null when missing or deactivated (the renderer then
   * falls back to the built-in generic layout).
   */
  async findActiveForContract(
    tradeDirection?: string | null,
    freightType?: string | null,
  ): Promise<ContractTemplate | null> {
    const code = contractTemplateCodeFor(tradeDirection, freightType);
    const template = await this.repository.findByCode(code);
    return template?.isActive ? template : null;
  }

  async update(code: string, dto: UpdateContractTemplateDto): Promise<ContractTemplate> {
    const template = await this.getByCode(code);
    if (dto.name !== undefined) template.name = dto.name;
    if (dto.description !== undefined) template.description = dto.description;
    if (dto.documentTitle !== undefined) template.documentTitle = dto.documentTitle;
    if (dto.whereasClauses !== undefined) template.whereasClauses = dto.whereasClauses;
    if (dto.isActive !== undefined) template.isActive = dto.isActive;
    return this.repository.saveTemplate(template);
  }

  async addArticle(code: string, dto: CreateArticleDto): Promise<ContractTemplate> {
    const template = await this.getByCode(code);
    const articles = this.sorted(template.articles);
    const article: ContractTemplateArticle = {
      id: randomUUID(),
      title: dto.title,
      body: dto.body,
      order: 0,
    };
    const index =
      dto.position && dto.position <= articles.length ? dto.position - 1 : articles.length;
    articles.splice(index, 0, article);
    template.articles = this.renumber(articles);
    return this.repository.saveTemplate(template);
  }

  async updateArticle(
    code: string,
    articleId: string,
    dto: UpdateArticleDto,
  ): Promise<ContractTemplate> {
    const template = await this.getByCode(code);
    const article = template.articles.find((item) => item.id === articleId);
    if (!article) {
      throw new NotFoundException(`Article ${articleId} not found on template ${code}`);
    }
    if (dto.title !== undefined) article.title = dto.title;
    if (dto.body !== undefined) article.body = dto.body;
    template.articles = this.renumber(this.sorted(template.articles));
    return this.repository.saveTemplate(template);
  }

  async removeArticle(code: string, articleId: string): Promise<ContractTemplate> {
    const template = await this.getByCode(code);
    const remaining = template.articles.filter((item) => item.id !== articleId);
    if (remaining.length === template.articles.length) {
      throw new NotFoundException(`Article ${articleId} not found on template ${code}`);
    }
    template.articles = this.renumber(this.sorted(remaining));
    return this.repository.saveTemplate(template);
  }

  /** Replace the full ordered article list (also how the editor reorders). */
  async replaceArticles(
    code: string,
    articles: ReplaceArticleDto[],
  ): Promise<ContractTemplate> {
    const template = await this.getByCode(code);
    template.articles = this.renumber(
      articles.map((item) => ({
        id: item.id ?? randomUUID(),
        title: item.title,
        body: item.body,
        order: 0,
      })),
    );
    return this.repository.saveTemplate(template);
  }

  /**
   * Render the template against a representative mock contract so admins can
   * see the final document without touching a real contract. Draft overrides
   * allow previewing unsaved editor state.
   */
  async preview(
    code: string,
    overrides?: PreviewContractTemplateDto,
  ): Promise<{ html: string }> {
    const template = await this.getByCode(code);

    const dynamicTemplate: ContractDynamicTemplateView = {
      code: template.code,
      name: overrides?.name ?? template.name,
      documentTitle: overrides?.documentTitle ?? template.documentTitle,
      whereasClauses: overrides?.whereasClauses ?? template.whereasClauses,
      articles: overrides?.articles
        ? overrides.articles.map((item, index) => ({
            id: item.id ?? randomUUID(),
            title: item.title,
            body: item.body,
            order: index + 1,
          }))
        : this.sorted(template.articles),
    };

    const view = this.buildMockView(template.code, dynamicTemplate);
    return { html: this.renderer.render(view) };
  }

  private buildMockView(
    code: ContractTemplateCode,
    dynamicTemplate: ContractDynamicTemplateView,
  ): ContractViewModel {
    const meta = getTemplateMeta(PREVIEW_TEMPLATE_KEYS[code]);
    const isBulk = code.endsWith("_BULK");
    const now = new Date();

    // Representative rate schedule so the admin preview shows the live-rate
    // table shape. Real contracts populate this from freight.rates (LIVE).
    const rateSchedule = this.mockRateSchedule(code, isBulk);

    return {
      bookingId: "00000000-0000-0000-0000-000000000000",
      reference: "EDR/CT/2026/0042",
      status: "CONTRACT_READY",
      templateKey: PREVIEW_TEMPLATE_KEYS[code],
      template: { ...meta, title: dynamicTemplate.name, templateFile: "edr-dynamic.hbs" },
      contractDate: now.toLocaleDateString("en-GB", {
        day: "numeric",
        month: "long",
        year: "numeric",
      }),
      contractYear: now.getFullYear(),
      client: {
        companyName: "Abyssinia Trading PLC",
        companyAddress: "Bole Sub-city, Woreda 03, H.No 1234, Addis Ababa",
        companyLocation: "Ethiopia",
        phone: "+251 91 123 4567",
        email: "logistics@abyssiniatrading.et",
        tinNumber: "0011223344",
        vatNumber: "VAT-556677",
        fanNumber: "FAN-889900",
        businessLicense: "BL/AA/12/345678",
      },
      provider: {
        name: "Ethio-Djibouti Standard Gauge Railway Share Company",
        address: "Nifas Silk Lafto Sub City, Addis Ababa, Ethiopia",
        phone: "+251 11 872 0000",
        email: "info@edr.gov.et",
        tinNumber: "—",
      },
      schedule: {
        originLabel: isBulk ? "Nagad Railway Station" : "SGTD Freight Station",
        destinationLabel: "Galaan Multipurpose Port (GMP)",
        tradeDirection: code.startsWith("IMPORT")
          ? "IMPORT"
          : code.startsWith("EXPORT")
            ? "EXPORT"
            : "DOMESTIC",
        freightType: isBulk ? "BULK" : "CONTAINER",
        serviceType: "Rail transport and customs clearance",
        scheduledDate: "—",
        contractType: "GENERAL",
        cargoDescription: isBulk ? "Steel billets — 2,800 MT" : "40ft containers — FMCG cargo",
        totalWeightVgm: "—",
        equipmentReturn: isBulk ? "—" : "With empty return",
        hazardousLabel: "No",
        firstMilePickupAddress: "—",
        lastMileDeliveryAddress: "—",
      },
      pricing: {
        lineItems: [],
        surcharges: [],
        totalAmount: 0,
        currency: "USD",
        equipmentReturn: isBulk ? "—" : "With empty return",
        originLabel: isBulk ? "Nagad Railway Station" : "SGTD Freight Station",
        destinationLabel: "Galaan Multipurpose Port (GMP)",
        containerLines: [],
      } as unknown as ContractViewModel["pricing"],
      rateSchedule,
      signatures: [],
      canSignCustomer: false,
      canSignStaff: false,
      hasContractDocument: false,
      hasCustomerSignature: false,
      hasStaffSignature: false,
      dynamicTemplate,
    };
  }

  /** Static, representative rate schedule for the admin preview only. */
  private mockRateSchedule(code: ContractTemplateCode, isBulk: boolean): RateSchedule {
    const dir = code.startsWith("IMPORT")
      ? "import"
      : code.startsWith("EXPORT")
        ? "export"
        : "domestic";
    const lane =
      dir === "export"
        ? "Galaan Multipurpose Port → SGTD"
        : dir === "domestic"
          ? "Mojo Dry Port → Dire Dawa"
          : "Negad → Mojo Dry Port";

    const freightLanes = isBulk
      ? [
          { route: lane, cargo: "Wheat", currency: "USD", amount: "100", unit: "per wagon" },
        ]
      : [
          { route: lane, cargo: "40ft GP", currency: "USD", amount: "200", unit: "per container" },
          { route: lane, cargo: "20ft GP", currency: "USD", amount: "180", unit: "per container" },
        ];

    return {
      freightLanes,
      additionalServices: [
        { route: "First-mile pickup by truck", cargo: "—", currency: "USD", amount: "50", unit: "per container" },
        { route: "Last-mile delivery by truck", cargo: "—", currency: "USD", amount: "50", unit: "per container" },
      ],
      surcharges: [
        { route: "Customs clearance service", cargo: "—", currency: "USD", amount: "120", unit: "flat" },
      ],
      isEmpty: false,
      currencyLabel: "USD",
    };
  }

  private assertCode(code: string): ContractTemplateCode {
    const upper = code?.toUpperCase() as ContractTemplateCode;
    if (!CONTRACT_TEMPLATE_CODES.includes(upper)) {
      throw new BadRequestException(
        `Unknown contract template code "${code}". Valid codes: ${CONTRACT_TEMPLATE_CODES.join(", ")}`,
      );
    }
    return upper;
  }

  private sorted(articles: ContractTemplateArticle[]): ContractTemplateArticle[] {
    return [...(articles ?? [])].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
  }

  private renumber(articles: ContractTemplateArticle[]): ContractTemplateArticle[] {
    return articles.map((article, index) => ({ ...article, order: index + 1 }));
  }
}
