import { Response, NextFunction } from 'express';
import { z } from 'zod';
import { currenciesService, CreateCurrencyDto, UpdateCurrencyDto } from './currencies.service.js';
import { currencyRatesService, CreateCurrencyRateDto } from './currency-rates.service.js';
import { countriesService, CreateCountryDto, UpdateCountryDto } from './countries.service.js';
import { statesService, CreateStateDto, UpdateStateDto } from './states.service.js';
import { uomService, CreateUomDto, UpdateUomDto, CreateUomCategoryDto, UpdateUomCategoryDto } from './uom.service.js';
import { productCategoriesService, CreateProductCategoryDto, UpdateProductCategoryDto } from './product-categories.service.js';
import { AuthenticatedRequest } from '../../shared/middleware/auth.middleware.js';
import { ValidationError } from '../../shared/errors/index.js';

// Schemas
const createCurrencySchema = z.object({
  code: z.string().length(3, 'El código debe tener 3 caracteres').toUpperCase(),
  name: z.string().min(1, 'El nombre es requerido').max(100),
  symbol: z.string().min(1).max(10),
  decimal_places: z.number().int().min(0).max(6).optional(),
  decimals: z.number().int().min(0).max(6).optional(), // Accept camelCase
}).refine((data) => data.decimal_places !== undefined || data.decimals !== undefined, {
  message: 'decimal_places or decimals is required',
});

const updateCurrencySchema = z.object({
  name: z.string().min(1).max(100).optional(),
  symbol: z.string().min(1).max(10).optional(),
  decimal_places: z.number().int().min(0).max(6).optional(),
  decimals: z.number().int().min(0).max(6).optional(), // Accept camelCase
  active: z.boolean().optional(),
});

// Currency Rate schemas
const createCurrencyRateSchema = z.object({
  from_currency_id: z.string().uuid().optional(),
  fromCurrencyId: z.string().uuid().optional(), // Accept camelCase
  to_currency_id: z.string().uuid().optional(),
  toCurrencyId: z.string().uuid().optional(), // Accept camelCase
  rate: z.number().positive('El tipo de cambio debe ser positivo'),
  valid_from: z.string().datetime().or(z.date()).optional(),
  validFrom: z.string().datetime().or(z.date()).optional(), // Accept camelCase
  valid_to: z.string().datetime().or(z.date()).nullable().optional(),
  validTo: z.string().datetime().or(z.date()).nullable().optional(), // Accept camelCase
  source: z.enum(['manual', 'api']).optional(),
}).refine(
  (data) => (data.from_currency_id || data.fromCurrencyId) && (data.to_currency_id || data.toCurrencyId),
  { message: 'from_currency_id y to_currency_id son requeridos' }
);

const createUomSchema = z.object({
  name: z.string().min(1, 'El nombre es requerido').max(100),
  code: z.string().min(1).max(20),
  category_id: z.string().uuid().optional(),
  categoryId: z.string().uuid().optional(), // Accept camelCase
  uom_type: z.enum(['reference', 'bigger', 'smaller']).optional(),
  uomType: z.enum(['reference', 'bigger', 'smaller']).optional(), // Accept camelCase
  ratio: z.number().positive().default(1),
}).refine((data) => data.category_id !== undefined || data.categoryId !== undefined, {
  message: 'category_id or categoryId is required',
});

const updateUomSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  ratio: z.number().positive().optional(),
  active: z.boolean().optional(),
});

const convertUomSchema = z.object({
  quantity: z.number({ required_error: 'La cantidad es requerida' }),
  fromUomId: z.string().uuid('fromUomId debe ser un UUID válido').optional(),
  toUomId: z.string().uuid('toUomId debe ser un UUID válido').optional(),
  from_uom_id: z.string().uuid('from_uom_id debe ser un UUID válido').optional(),
  to_uom_id: z.string().uuid('to_uom_id debe ser un UUID válido').optional(),
  fromCode: z.string().min(1).optional(),
  toCode: z.string().min(1).optional(),
  from_code: z.string().min(1).optional(),
  to_code: z.string().min(1).optional(),
}).refine(
  (data) => {
    // Must have either UoM IDs or codes
    const hasIds = (data.fromUomId || data.from_uom_id) && (data.toUomId || data.to_uom_id);
    const hasCodes = (data.fromCode || data.from_code) && (data.toCode || data.to_code);
    return hasIds || hasCodes;
  },
  { message: 'Debe proporcionar fromUomId/toUomId o fromCode/toCode' }
);

const createUomCategorySchema = z.object({
  name: z.string().min(1, 'El nombre es requerido').max(100),
  description: z.string().max(500).optional(),
});

const updateUomCategorySchema = z.object({
  name: z.string().min(1).max(100).optional(),
  description: z.string().max(500).optional().nullable(),
});

const createCategorySchema = z.object({
  name: z.string().min(1, 'El nombre es requerido').max(100),
  code: z.string().min(1).max(50),
  parent_id: z.string().uuid().optional(),
  parentId: z.string().uuid().optional(), // Accept camelCase
});

const updateCategorySchema = z.object({
  name: z.string().min(1).max(100).optional(),
  parent_id: z.string().uuid().optional().nullable(),
  parentId: z.string().uuid().optional().nullable(), // Accept camelCase
  active: z.boolean().optional(),
});

// Country schemas
const createCountrySchema = z.object({
  code: z.string().length(2, 'El código debe tener 2 caracteres').toUpperCase(),
  name: z.string().min(1, 'El nombre es requerido').max(255),
  phone_code: z.string().max(10).optional(),
  phoneCode: z.string().max(10).optional(), // Accept camelCase
  currency_code: z.string().length(3).optional(),
  currencyCode: z.string().length(3).optional(), // Accept camelCase
});

const updateCountrySchema = z.object({
  name: z.string().min(1).max(255).optional(),
  phone_code: z.string().max(10).optional().nullable(),
  phoneCode: z.string().max(10).optional().nullable(), // Accept camelCase
  currency_code: z.string().length(3).optional().nullable(),
  currencyCode: z.string().length(3).optional().nullable(), // Accept camelCase
});

// State schemas
const createStateSchema = z.object({
  country_id: z.string().uuid().optional(),
  countryId: z.string().uuid().optional(), // Accept camelCase
  code: z.string().min(1, 'El código es requerido').max(10).toUpperCase(),
  name: z.string().min(1, 'El nombre es requerido').max(100),
  active: z.boolean().optional(),
  is_active: z.boolean().optional(), // Accept snake_case
}).refine((data) => data.country_id !== undefined || data.countryId !== undefined, {
  message: 'country_id o countryId es requerido',
});

const updateStateSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  code: z.string().min(1).max(10).toUpperCase().optional(),
  active: z.boolean().optional(),
  is_active: z.boolean().optional(), // Accept snake_case
});

class CoreController {
  // ========== CURRENCIES ==========
  async getCurrencies(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const activeOnly = req.query.active === 'true';
      const currencies = await currenciesService.findAll(activeOnly);
      res.json({ success: true, data: currencies });
    } catch (error) {
      next(error);
    }
  }

  async getCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const currency = await currenciesService.findById(req.params.id);
      res.json({ success: true, data: currency });
    } catch (error) {
      next(error);
    }
  }

  async createCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createCurrencySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de moneda inválidos', parseResult.error.errors);
      }
      const dto: CreateCurrencyDto = parseResult.data;
      const currency = await currenciesService.create(dto);
      res.status(201).json({ success: true, data: currency, message: 'Moneda creada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async updateCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = updateCurrencySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de moneda inválidos', parseResult.error.errors);
      }
      const dto: UpdateCurrencyDto = parseResult.data;
      const currency = await currenciesService.update(req.params.id, dto);
      res.json({ success: true, data: currency, message: 'Moneda actualizada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  // ========== CURRENCY RATES ==========
  async getCurrencyRates(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const fromCurrencyId = (req.query.from_currency_id || req.query.fromCurrencyId) as string | undefined;
      const toCurrencyId = (req.query.to_currency_id || req.query.toCurrencyId) as string | undefined;
      const rates = await currencyRatesService.findAll(fromCurrencyId, toCurrencyId);
      res.json({ success: true, data: rates });
    } catch (error) {
      next(error);
    }
  }

  async getLatestCurrencyRate(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const from = (req.query.from || req.query.fromCode) as string;
      const to = (req.query.to || req.query.toCode) as string;

      if (!from || !to) {
        throw new ValidationError('Los parámetros from y to son requeridos');
      }

      const rate = await currencyRatesService.findLatestRate(from, to);
      res.json({
        success: true,
        data: {
          from: from.toUpperCase(),
          to: to.toUpperCase(),
          rate,
        },
      });
    } catch (error) {
      next(error);
    }
  }

  async createCurrencyRate(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createCurrencyRateSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de tipo de cambio inválidos', parseResult.error.errors);
      }
      const dto: CreateCurrencyRateDto = parseResult.data;
      const rate = await currencyRatesService.create(dto);
      res.status(201).json({ success: true, data: rate, message: 'Tipo de cambio creado exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async convertCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const amount = parseFloat(req.query.amount as string);
      const from = (req.query.from || req.query.fromCode) as string;
      const to = (req.query.to || req.query.toCode) as string;

      if (isNaN(amount)) {
        throw new ValidationError('El parámetro amount debe ser un número válido');
      }
      if (!from || !to) {
        throw new ValidationError('Los parámetros from y to son requeridos');
      }

      const result = await currencyRatesService.convert(amount, from, to);
      res.json({ success: true, data: result });
    } catch (error) {
      next(error);
    }
  }

  async getHistoricalCurrencyRates(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const from = (req.query.from || req.query.fromCode) as string;
      const to = (req.query.to || req.query.toCode) as string;
      const startDate = req.query.start_date || req.query.startDate;
      const endDate = req.query.end_date || req.query.endDate;

      if (!from || !to) {
        throw new ValidationError('Los parámetros from y to son requeridos');
      }
      if (!startDate || !endDate) {
        throw new ValidationError('Los parámetros start_date y end_date son requeridos');
      }

      const rates = await currencyRatesService.getHistoricalRates(
        from,
        to,
        new Date(startDate as string),
        new Date(endDate as string)
      );
      res.json({ success: true, data: rates });
    } catch (error) {
      next(error);
    }
  }

  // ========== COUNTRIES ==========
  async getCountries(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const countries = await countriesService.findAll();
      res.json({ success: true, data: countries });
    } catch (error) {
      next(error);
    }
  }

  async getCountry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const country = await countriesService.findById(req.params.id);
      res.json({ success: true, data: country });
    } catch (error) {
      next(error);
    }
  }

  async getCountryWithStates(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const country = await countriesService.findWithStates(req.params.id);
      res.json({ success: true, data: country });
    } catch (error) {
      next(error);
    }
  }

  async createCountry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createCountrySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de país inválidos', parseResult.error.errors);
      }
      const dto: CreateCountryDto = parseResult.data;
      const country = await countriesService.create(dto);
      res.status(201).json({ success: true, data: country, message: 'País creado exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async updateCountry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = updateCountrySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de país inválidos', parseResult.error.errors);
      }
      const dto: UpdateCountryDto = parseResult.data;
      const country = await countriesService.update(req.params.id, dto);
      res.json({ success: true, data: country, message: 'País actualizado exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  // ========== STATES ==========
  async getStates(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const countryId = (req.query.country_id || req.query.countryId) as string | undefined;
      const activeOnly = req.query.active === 'true';
      const states = await statesService.findAll(countryId, activeOnly);
      res.json({ success: true, data: states });
    } catch (error) {
      next(error);
    }
  }

  async getState(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const state = await statesService.findById(req.params.id);
      res.json({ success: true, data: state });
    } catch (error) {
      next(error);
    }
  }

  async getStatesByCountry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const activeOnly = req.query.active === 'true';
      const states = await statesService.findByCountry(req.params.id, activeOnly);
      res.json({ success: true, data: states });
    } catch (error) {
      next(error);
    }
  }

  async createState(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createStateSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de estado inválidos', parseResult.error.errors);
      }
      const dto: CreateStateDto = parseResult.data;
      const state = await statesService.create(dto);
      res.status(201).json({ success: true, data: state, message: 'Estado creado exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async updateState(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = updateStateSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de estado inválidos', parseResult.error.errors);
      }
      const dto: UpdateStateDto = parseResult.data;
      const state = await statesService.update(req.params.id, dto);
      res.json({ success: true, data: state, message: 'Estado actualizado exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  // ========== UOM CATEGORIES ==========
  async getUomCategories(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const activeOnly = req.query.active === 'true';
      const categories = await uomService.findAllCategories(activeOnly);
      res.json({ success: true, data: categories });
    } catch (error) {
      next(error);
    }
  }

  async getUomCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const category = await uomService.findCategoryById(req.params.id);
      res.json({ success: true, data: category });
    } catch (error) {
      next(error);
    }
  }

  // ========== UOM ==========
  async getUoms(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const activeOnly = req.query.active === 'true';
      const categoryId = req.query.category_id as string | undefined;
      const uoms = await uomService.findAll(categoryId, activeOnly);
      res.json({ success: true, data: uoms });
    } catch (error) {
      next(error);
    }
  }

  async getUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const uom = await uomService.findById(req.params.id);
      res.json({ success: true, data: uom });
    } catch (error) {
      next(error);
    }
  }

  async createUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createUomSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de UdM inválidos', parseResult.error.errors);
      }
      const dto: CreateUomDto = parseResult.data;
      const uom = await uomService.create(dto);
      res.status(201).json({ success: true, data: uom, message: 'Unidad de medida creada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async updateUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = updateUomSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de UdM inválidos', parseResult.error.errors);
      }
      const dto: UpdateUomDto = parseResult.data;
      const uom = await uomService.update(req.params.id, dto);
      res.json({ success: true, data: uom, message: 'Unidad de medida actualizada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async convertUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = convertUomSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de conversión inválidos', parseResult.error.errors);
      }

      const data = parseResult.data;
      const quantity = data.quantity;

      // Support both snake_case and camelCase
      const fromUomId = data.fromUomId || data.from_uom_id;
      const toUomId = data.toUomId || data.to_uom_id;
      const fromCode = data.fromCode || data.from_code;
      const toCode = data.toCode || data.to_code;

      let result;
      if (fromUomId && toUomId) {
        result = await uomService.convert(quantity, fromUomId, toUomId);
      } else if (fromCode && toCode) {
        result = await uomService.convertByCode(quantity, fromCode, toCode);
      } else {
        throw new ValidationError('Debe proporcionar fromUomId/toUomId o fromCode/toCode');
      }

      res.json({
        success: true,
        data: result,
        message: 'Conversión realizada exitosamente',
      });
    } catch (error) {
      next(error);
    }
  }

  async getUomCategoryReference(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const referenceUom = await uomService.getReferenceUom(req.params.id);
      res.json({ success: true, data: referenceUom });
    } catch (error) {
      next(error);
    }
  }

  async createUomCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createUomCategorySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de categoría de UdM inválidos', parseResult.error.errors);
      }
      const dto: CreateUomCategoryDto = parseResult.data;
      const category = await uomService.createCategory(dto);
      res.status(201).json({ success: true, data: category, message: 'Categoría de UdM creada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async updateUomCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = updateUomCategorySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de categoría de UdM inválidos', parseResult.error.errors);
      }
      const dto: UpdateUomCategoryDto = parseResult.data;
      const category = await uomService.updateCategory(req.params.id, dto);
      res.json({ success: true, data: category, message: 'Categoría de UdM actualizada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async deleteUomCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      await uomService.deleteCategory(req.params.id);
      res.json({ success: true, message: 'Categoría de UdM eliminada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  // ========== PRODUCT CATEGORIES ==========
  async getProductCategories(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const activeOnly = req.query.active === 'true';
      const parentId = req.query.parent_id as string | undefined;
      const categories = await productCategoriesService.findAll(req.tenantId!, parentId, activeOnly);
      res.json({ success: true, data: categories });
    } catch (error) {
      next(error);
    }
  }

  async getProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const category = await productCategoriesService.findById(req.params.id, req.tenantId!);
      res.json({ success: true, data: category });
    } catch (error) {
      next(error);
    }
  }

  async createProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = createCategorySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de categoría inválidos', parseResult.error.errors);
      }
      const dto: CreateProductCategoryDto = parseResult.data;
      const category = await productCategoriesService.create(dto, req.tenantId!, req.user!.userId);
      res.status(201).json({ success: true, data: category, message: 'Categoría creada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async updateProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const parseResult = updateCategorySchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new ValidationError('Datos de categoría inválidos', parseResult.error.errors);
      }
      const dto: UpdateProductCategoryDto = parseResult.data;
      const category = await productCategoriesService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
      res.json({ success: true, data: category, message: 'Categoría actualizada exitosamente' });
    } catch (error) {
      next(error);
    }
  }

  async deleteProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      await productCategoriesService.delete(req.params.id, req.tenantId!);
      res.json({ success: true, message: 'Categoría eliminada exitosamente' });
    } catch (error) {
      next(error);
    }
  }
}

export const coreController = new CoreController();
