import { ACCOUNT_CLASSES } from "../../../shared/constants.js";

import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import {
  formatDateOnly,
  syncFiscalYearPeriods,
} from "../utils/accountingPeriods.js";
import { logAudit } from "../utils/auditLog.js";
import { generateFiscalDeadlines } from "../utils/fiscalDeadlines.js";
import { ensureG50terDeclarations } from "../utils/fiscalG50ter.js";
import {
  canManageAdmin,
  fetchRolePermissions,
  syncDefaultRoles,
} from "../utils/permissions.js";

const cleanString = (value) => String(value ?? "").trim();

const sanitizeCompany = (company) => ({
  id: Number(company.id),
  name: company.name,
  nif: company.nif,
  rc: company.rc,
  address: company.address,
  city: company.city,
  phone: company.phone,
  email: company.email,
  logo_url: company.logo_url,
  currency: company.currency,
  fiscal_year_start: company.fiscal_year_start,
  fiscal_year_start_month: Number(String(company.fiscal_year_start).slice(5, 7)),
  created_at: company.created_at ? new Date(company.created_at).toISOString() : null,
});

const mapCompanyRow = (row) => ({
  id: Number(row.id),
  name: row.name,
  nif: row.nif,
  rc: row.rc,
  address: row.address,
  city: row.city,
  phone: row.phone,
  email: row.email,
  logo_url: row.logo_url,
  currency: row.currency,
  fiscal_year_start: row.fiscal_year_start,
  fiscal_year_start_month: Number(String(row.fiscal_year_start).slice(5, 7)),
  created_at: row.created_at ? new Date(row.created_at).toISOString() : null,
  users_count: Number(row.users_count || 0),
  current_fiscal_year: row.current_fiscal_year_id
    ? {
        id: Number(row.current_fiscal_year_id),
        label: row.current_fiscal_year_label,
        start_date: row.current_fiscal_year_start_date,
        end_date: row.current_fiscal_year_end_date,
        is_closed: row.current_fiscal_year_is_closed,
      }
    : null,
});

const ensureAdminPermission = async (db, req) => {
  await syncDefaultRoles(db);
  const permissions = await fetchRolePermissions(db, req.user?.role);

  if (!canManageAdmin(req.user?.role, permissions)) {
    throw new ApiError(403, "Accès refusé à la gestion des sociétés.");
  }
};

const buildFiscalYearWindow = (startMonth, now = new Date()) => {
  const normalizedMonth = Number(startMonth || 1);

  if (!Number.isInteger(normalizedMonth) || normalizedMonth < 1 || normalizedMonth > 12) {
    throw new ApiError(400, "Le mois de démarrage de l'exercice est invalide.");
  }

  const currentYear = now.getUTCFullYear();
  const currentMonth = now.getUTCMonth() + 1;
  const startYear = currentMonth >= normalizedMonth ? currentYear : currentYear - 1;
  const startDate = new Date(Date.UTC(startYear, normalizedMonth - 1, 1));
  const endDate = new Date(Date.UTC(startYear + 1, normalizedMonth - 1, 0));
  const endYear = endDate.getUTCFullYear();

  return {
    label: startYear === endYear ? String(startYear) : `${startYear}-${endYear}`,
    start_date: formatDateOnly(startDate),
    end_date: formatDateOnly(endDate),
  };
};

const insertAccountClasses = async (db, companyId) => {
  for (const accountClass of ACCOUNT_CLASSES) {
    await db.query(
      `
        INSERT INTO account_classes (company_id, code, label)
        VALUES ($1, $2, $3)
        ON CONFLICT (company_id, code) DO NOTHING
      `,
      [companyId, accountClass.code, accountClass.label],
    );
  }
};

const fetchCompany = async (db, companyId) => {
  const { rows } = await db.query(
    `
      SELECT
        c.id,
        c.name,
        c.nif,
        c.rc,
        c.address,
        c.city,
        c.phone,
        c.email,
        c.logo_url,
        c.currency,
        TO_CHAR(c.fiscal_year_start, 'YYYY-MM-DD') AS fiscal_year_start,
        c.created_at,
        COALESCE(user_stats.users_count, 0) AS users_count,
        fy.id AS current_fiscal_year_id,
        fy.label AS current_fiscal_year_label,
        TO_CHAR(fy.start_date, 'YYYY-MM-DD') AS current_fiscal_year_start_date,
        TO_CHAR(fy.end_date, 'YYYY-MM-DD') AS current_fiscal_year_end_date,
        fy.is_closed AS current_fiscal_year_is_closed
      FROM companies c
      LEFT JOIN LATERAL (
        SELECT COUNT(DISTINCT uc.user_id)::int AS users_count
        FROM user_companies uc
        WHERE uc.company_id = c.id
      ) AS user_stats
        ON TRUE
      LEFT JOIN LATERAL (
        SELECT id, label, start_date, end_date, is_closed
        FROM fiscal_years
        WHERE company_id = c.id
        ORDER BY is_current DESC, start_date DESC
        LIMIT 1
      ) AS fy
        ON TRUE
      WHERE c.id = $1
      LIMIT 1
    `,
    [companyId],
  );

  return rows[0] ? mapCompanyRow(rows[0]) : null;
};

const mapUniqueConstraintError = (error) => {
  if (error?.constraint === "idx_companies_nif_unique") {
    return new ApiError(409, "Ce NIF existe déjà.");
  }

  if (error?.constraint === "idx_companies_rc_unique") {
    return new ApiError(409, "Ce RC existe déjà.");
  }

  return error;
};

export const companiesController = {
  list: async (req, res) => {
    await ensureAdminPermission(pool, req);

    const search = cleanString(req.query.search);
    const { rows } = await pool.query(
      `
        SELECT
          c.id,
          c.name,
          c.nif,
          c.rc,
          c.address,
          c.city,
          c.phone,
          c.email,
          c.logo_url,
          c.currency,
          TO_CHAR(c.fiscal_year_start, 'YYYY-MM-DD') AS fiscal_year_start,
          c.created_at,
          COALESCE(user_stats.users_count, 0) AS users_count,
          fy.id AS current_fiscal_year_id,
          fy.label AS current_fiscal_year_label,
          TO_CHAR(fy.start_date, 'YYYY-MM-DD') AS current_fiscal_year_start_date,
          TO_CHAR(fy.end_date, 'YYYY-MM-DD') AS current_fiscal_year_end_date,
          fy.is_closed AS current_fiscal_year_is_closed
        FROM companies c
        LEFT JOIN LATERAL (
          SELECT COUNT(DISTINCT uc.user_id)::int AS users_count
          FROM user_companies uc
          WHERE uc.company_id = c.id
        ) AS user_stats
          ON TRUE
        LEFT JOIN LATERAL (
          SELECT id, label, start_date, end_date, is_closed
          FROM fiscal_years
          WHERE company_id = c.id
          ORDER BY is_current DESC, start_date DESC
          LIMIT 1
        ) AS fy
          ON TRUE
        WHERE (
          $1::text = ''
          OR c.name ILIKE '%' || $1 || '%'
          OR c.nif ILIKE '%' || $1 || '%'
          OR c.rc ILIKE '%' || $1 || '%'
          OR COALESCE(c.city, '') ILIKE '%' || $1 || '%'
        )
        ORDER BY c.name ASC
      `,
      [search],
    );

    res.json({
      items: rows.map(mapCompanyRow),
    });
  },

  getById: async (req, res) => {
    await ensureAdminPermission(pool, req);

    const companyId = Number(req.params.id || 0);
    const company = await fetchCompany(pool, companyId);

    if (!company) {
      throw new ApiError(404, "Société introuvable.");
    }

    res.json({
      item: company,
    });
  },

  create: async (req, res) => {
    await ensureAdminPermission(pool, req);

    const name = cleanString(req.body.name);
    const nif = cleanString(req.body.nif);
    const rc = cleanString(req.body.rc);
    const address = cleanString(req.body.address);
    const city = cleanString(req.body.city);
    const phone = cleanString(req.body.phone);
    const email = cleanString(req.body.email).toLowerCase();
    const logoUrl = cleanString(req.body.logo_url);
    const currency = cleanString(req.body.currency || "DZD").toUpperCase() || "DZD";
    const fiscalYearStartMonth = Number(req.body.fiscal_year_start_month || 1);

    if (!name) {
      throw new ApiError(400, "Le nom de la société est obligatoire.");
    }

    if (!nif) {
      throw new ApiError(400, "Le NIF est obligatoire.");
    }

    if (!rc) {
      throw new ApiError(400, "Le RC est obligatoire.");
    }

    const fiscalYear = buildFiscalYearWindow(fiscalYearStartMonth);
    const fiscalYearStart = `${fiscalYear.start_date.slice(0, 4)}-${String(fiscalYearStartMonth).padStart(2, "0")}-01`;
    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      const { rows } = await client.query(
        `
          INSERT INTO companies (
            name,
            nif,
            rc,
            address,
            city,
            phone,
            email,
            logo_url,
            currency,
            fiscal_year_start
          )
          VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
          RETURNING
            id,
            name,
            nif,
            rc,
            address,
            city,
            phone,
            email,
            logo_url,
            currency,
            TO_CHAR(fiscal_year_start, 'YYYY-MM-DD') AS fiscal_year_start,
            created_at
        `,
        [
          name,
          nif,
          rc,
          address || null,
          city || null,
          phone || null,
          email || null,
          logoUrl || null,
          currency,
          fiscalYearStart,
        ],
      );

      const company = rows[0];

      await insertAccountClasses(client, company.id);

      const { rows: fiscalYearRows } = await client.query(
        `
          INSERT INTO fiscal_years (
            company_id,
            label,
            start_date,
            end_date,
            is_closed,
            is_current
          )
          VALUES ($1, $2, $3, $4, FALSE, TRUE)
          RETURNING
            id,
            company_id,
            label,
            TO_CHAR(start_date, 'YYYY-MM-DD') AS start_date
        `,
        [company.id, fiscalYear.label, fiscalYear.start_date, fiscalYear.end_date],
      );

      const currentFiscalYear = fiscalYearRows[0];
      const generatedPeriods = await syncFiscalYearPeriods(client, currentFiscalYear);
      await ensureG50terDeclarations(client, {
        companyId: company.id,
        fiscalYear: currentFiscalYear,
        userId: req.user?.sub || null,
      });
      await generateFiscalDeadlines(client, {
        companyId: company.id,
        fiscalYear: currentFiscalYear,
        userId: req.user?.sub || req.user?.id || null,
        io: req.app.get("io"),
      });

      if (req.user?.sub) {
        await client.query(
          `
            INSERT INTO user_companies (user_id, company_id)
            VALUES ($1, $2)
            ON CONFLICT (user_id, company_id) DO NOTHING
          `,
          [req.user.sub, company.id],
        );
      }

      await logAudit(client, {
        companyId: company.id,
        userId: req.user?.sub || null,
        action: "companies.create",
        entityType: "company",
        entityId: company.id,
        newData: {
          ...sanitizeCompany(company),
          generated_account_classes: ACCOUNT_CLASSES.length,
          current_fiscal_year: {
            label: fiscalYear.label,
            start_date: fiscalYear.start_date,
            end_date: fiscalYear.end_date,
          },
          generated_periods: generatedPeriods.length,
        },
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      const item = await fetchCompany(pool, company.id);

      res.status(201).json({
        message: "Société créée avec succès.",
        item,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw mapUniqueConstraintError(error);
    } finally {
      client.release();
    }
  },
};
