package repository

import (
	"context"
	"time"

	"apskel-pos-be/internal/entities"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type AnalyticsRepository interface {
	GetPaymentMethodAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.PaymentMethodAnalytics, error)
	GetSalesAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.SalesAnalytics, error)
	GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error)
	GetProductAnalyticsPerCategory(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.ProductAnalyticsPerCategory, error)
	GetDashboardOverview(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.DashboardOverview, error)
	GetProfitLossAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.ProfitLossAnalytics, error)
}

type AnalyticsRepositoryImpl struct {
	db *gorm.DB
}

func NewAnalyticsRepositoryImpl(db *gorm.DB) *AnalyticsRepositoryImpl {
	return &AnalyticsRepositoryImpl{
		db: db,
	}
}

func (r *AnalyticsRepositoryImpl) GetPaymentMethodAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.PaymentMethodAnalytics, error) {
	var results []*entities.PaymentMethodAnalytics

	query := r.db.WithContext(ctx).
		Table("payments p").
		Select(`
			pm.id as payment_method_id,
			pm.name as payment_method_name,
			pm.type as payment_method_type,
			COALESCE(SUM(p.amount), 0) as total_amount,
			COUNT(DISTINCT p.order_id) as order_count,
			COUNT(p.id) as payment_count
		`).
		Joins("JOIN payment_methods pm ON p.payment_method_id = pm.id").
		Joins("JOIN orders o ON p.order_id = o.id").
		Where("o.organization_id = ?", organizationID).
		Where("p.status = ?", entities.PaymentTransactionStatusCompleted).
		Where("p.created_at >= ? AND p.created_at <= ?", dateFrom, dateTo)

	if outletID != nil {
		query = query.Where("o.outlet_id = ?", *outletID)
	}

	err := query.
		Group("pm.id, pm.name, pm.type").
		Order("total_amount DESC").
		Scan(&results).Error

	return results, err
}

func (r *AnalyticsRepositoryImpl) GetSalesAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.SalesAnalytics, error) {
	var results []*entities.SalesAnalytics

	var dateFormat string
	switch groupBy {
	case "hour":
		dateFormat = "DATE_TRUNC('hour', o.created_at)"
	case "week":
		dateFormat = "DATE_TRUNC('week', o.created_at)"
	case "month":
		dateFormat = "DATE_TRUNC('month', o.created_at)"
	default:
		dateFormat = "DATE(o.created_at)"
	}

	query := r.db.WithContext(ctx).
		Table("orders o").
		Select(`
			`+dateFormat+` as date,
			COALESCE(SUM(o.total_amount), 0) as sales,
			COUNT(o.id) as orders,
			COALESCE(SUM(oi.quantity), 0) as items,
			COALESCE(SUM(o.tax_amount), 0) as tax,
			COALESCE(SUM(o.discount_amount), 0) as discount,
			COALESCE(SUM(o.total_amount - o.tax_amount - o.discount_amount), 0) as net_sales
		`).
		Joins("LEFT JOIN order_items oi ON o.id = oi.order_id").
		Where("o.organization_id = ?", organizationID).
		Where("o.is_void = ?", false).
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo)

	if outletID != nil {
		query = query.Where("o.outlet_id = ?", *outletID)
	}

	err := query.
		Group("date").
		Order("date ASC").
		Scan(&results).Error

	return results, err
}

func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error) {
	var results []*entities.ProductAnalytics

	query := r.db.WithContext(ctx).
		Table("order_items oi").
		Select(`
			p.id as product_id,
			p.name as product_name,
			c.id as category_id,
			c.name as category_name,
			COALESCE(SUM(oi.quantity), 0) as quantity_sold,
			COALESCE(SUM(oi.total_price), 0) as revenue,
			CASE 
				WHEN SUM(oi.quantity) > 0 THEN COALESCE(SUM(oi.total_price), 0) / SUM(oi.quantity)
				ELSE 0 
			END as average_price,
			COUNT(DISTINCT oi.order_id) as order_count
		`).
		Joins("JOIN products p ON oi.product_id = p.id").
		Joins("JOIN categories c ON p.category_id = c.id").
		Joins("JOIN orders o ON oi.order_id = o.id").
		Where("o.organization_id = ?", organizationID).
		Where("o.is_void = ?", false).
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo)

	if outletID != nil {
		query = query.Where("o.outlet_id = ?", *outletID)
	}

	err := query.
		Group("p.id, p.name, c.id, c.name").
		Order("revenue DESC").
		Limit(limit).
		Scan(&results).Error

	return results, err
}

func (r *AnalyticsRepositoryImpl) GetProductAnalyticsPerCategory(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.ProductAnalyticsPerCategory, error) {
	var results []*entities.ProductAnalyticsPerCategory

	query := r.db.WithContext(ctx).
		Table("order_items oi").
		Select(`
			c.id as category_id,
			c.name as category_name,
			COALESCE(SUM(oi.total_price), 0) as total_revenue,
			COALESCE(SUM(oi.quantity), 0) as total_quantity,
			COUNT(DISTINCT p.id) as product_count,
			COUNT(DISTINCT oi.order_id) as order_count
		`).
		Joins("JOIN products p ON oi.product_id = p.id").
		Joins("JOIN categories c ON p.category_id = c.id").
		Joins("JOIN orders o ON oi.order_id = o.id").
		Where("o.organization_id = ?", organizationID).
		Where("o.is_void = ?", false).
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo)

	if outletID != nil {
		query = query.Where("o.outlet_id = ?", *outletID)
	}

	err := query.
		Group("c.id, c.name").
		Order("total_revenue DESC").
		Scan(&results).Error

	return results, err
}

func (r *AnalyticsRepositoryImpl) GetDashboardOverview(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.DashboardOverview, error) {
	var result entities.DashboardOverview

	query := r.db.WithContext(ctx).
		Table("orders o").
		Select(`
			COALESCE(SUM(CASE WHEN o.is_void = false THEN o.total_amount ELSE 0 END), 0) as total_sales,
			COUNT(CASE WHEN o.is_void = false THEN o.id END) as total_orders,
			CASE 
				WHEN COUNT(CASE WHEN o.is_void = false THEN o.id END) > 0 
				THEN COALESCE(SUM(CASE WHEN o.is_void = false THEN o.total_amount ELSE 0 END), 0) / COUNT(CASE WHEN o.is_void = false THEN o.id END)
				ELSE 0 
			END as average_order_value,
			COUNT(DISTINCT o.customer_id) as total_customers,
			COUNT(CASE WHEN o.is_void = true THEN o.id END) as voided_orders,
			COUNT(CASE WHEN o.is_refund = true THEN o.id END) as refunded_orders
		`).
		Where("o.organization_id = ?", organizationID).
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo)

	if outletID != nil {
		query = query.Where("o.outlet_id = ?", *outletID)
	}

	err := query.Scan(&result).Error
	if err != nil {
		return nil, err
	}

	return &result, nil
}

func (r *AnalyticsRepositoryImpl) GetProfitLossAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.ProfitLossAnalytics, error) {
	// Summary query
	var summary entities.ProfitLossSummary

	summaryQuery := r.db.WithContext(ctx).
		Table("orders o").
		Select(`
			COALESCE(SUM(o.total_amount), 0) as total_revenue,
			COALESCE(SUM(o.total_cost), 0) as total_cost,
			COALESCE(SUM(o.total_amount - o.total_cost), 0) as gross_profit,
			CASE 
				WHEN SUM(o.total_amount) > 0 
				THEN (SUM(o.total_amount - o.total_cost) / SUM(o.total_amount)) * 100 
				ELSE 0 
			END as gross_profit_margin,
			COALESCE(SUM(o.tax_amount), 0) as total_tax,
			COALESCE(SUM(o.discount_amount), 0) as total_discount,
			COALESCE(SUM(o.total_amount - o.total_cost - o.discount_amount), 0) as net_profit,
			CASE 
				WHEN SUM(o.total_amount) > 0 
				THEN (SUM(o.total_amount - o.total_cost - o.discount_amount) / SUM(o.total_amount)) * 100 
				ELSE 0 
			END as net_profit_margin,
			COUNT(o.id) as total_orders,
			CASE 
				WHEN COUNT(o.id) > 0 
				THEN SUM(o.total_amount - o.total_cost - o.discount_amount) / COUNT(o.id) 
				ELSE 0 
			END as average_profit,
			CASE 
				WHEN SUM(o.total_cost) > 0 
				THEN (SUM(o.total_amount - o.total_cost) / SUM(o.total_cost)) * 100 
				ELSE 0 
			END as profitability_ratio
		`).
		Where("o.organization_id = ?", organizationID).
		Where("o.status = ?", entities.OrderStatusCompleted).
		Where("o.payment_status = ?", entities.PaymentStatusCompleted).
		Where("o.is_void = false AND o.is_refund = false").
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo)

	if outletID != nil {
		summaryQuery = summaryQuery.Where("o.outlet_id = ?", *outletID)
	}

	err := summaryQuery.Scan(&summary).Error
	if err != nil {
		return nil, err
	}

	// Time series data query
	var timeFormat string
	switch groupBy {
	case "hour":
		timeFormat = "DATE_TRUNC('hour', o.created_at)"
	case "week":
		timeFormat = "DATE_TRUNC('week', o.created_at)"
	case "month":
		timeFormat = "DATE_TRUNC('month', o.created_at)"
	default: // day
		timeFormat = "DATE_TRUNC('day', o.created_at)"
	}

	var data []entities.ProfitLossData

	dataQuery := r.db.WithContext(ctx).
		Table("orders o").
		Select(`
			`+timeFormat+` as date,
			COALESCE(SUM(o.total_amount), 0) as revenue,
			COALESCE(SUM(o.total_cost), 0) as cost,
			COALESCE(SUM(o.total_amount - o.total_cost), 0) as gross_profit,
			CASE 
				WHEN SUM(o.total_amount) > 0 
				THEN (SUM(o.total_amount - o.total_cost) / SUM(o.total_amount)) * 100 
				ELSE 0 
			END as gross_profit_margin,
			COALESCE(SUM(o.tax_amount), 0) as tax,
			COALESCE(SUM(o.discount_amount), 0) as discount,
			COALESCE(SUM(o.total_amount - o.total_cost - o.discount_amount), 0) as net_profit,
			CASE 
				WHEN SUM(o.total_amount) > 0 
				THEN (SUM(o.total_amount - o.total_cost - o.discount_amount) / SUM(o.total_amount)) * 100 
				ELSE 0 
			END as net_profit_margin,
			COUNT(o.id) as orders
		`).
		Where("o.organization_id = ?", organizationID).
		Where("o.status = ?", entities.OrderStatusCompleted).
		Where("o.payment_status = ?", entities.PaymentStatusCompleted).
		Where("o.is_void = false AND o.is_refund = false").
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo).
		Group(timeFormat).
		Order(timeFormat)

	if outletID != nil {
		dataQuery = dataQuery.Where("o.outlet_id = ?", *outletID)
	}

	err = dataQuery.Scan(&data).Error
	if err != nil {
		return nil, err
	}

	// Product profit data query
	var productData []entities.ProductProfitData

	productQuery := r.db.WithContext(ctx).
		Table("order_items oi").
		Select(`
			p.id as product_id,
			p.name as product_name,
			c.id as category_id,
			c.name as category_name,
			SUM(oi.quantity) as quantity_sold,
			SUM(oi.total_price) as revenue,
			SUM(oi.total_cost) as cost,
			SUM(oi.total_price - oi.total_cost) as gross_profit,
			CASE 
				WHEN SUM(oi.total_price) > 0 
				THEN (SUM(oi.total_price - oi.total_cost) / SUM(oi.total_price)) * 100 
				ELSE 0 
			END as gross_profit_margin,
			AVG(oi.unit_price) as average_price,
			AVG(oi.unit_cost) as average_cost,
			AVG(oi.unit_price - oi.unit_cost) as profit_per_unit
		`).
		Joins("JOIN orders o ON oi.order_id = o.id").
		Joins("JOIN products p ON oi.product_id = p.id").
		Joins("JOIN categories c ON p.category_id = c.id").
		Where("o.organization_id = ?", organizationID).
		Where("o.status = ?", entities.OrderStatusCompleted).
		Where("o.payment_status = ?", entities.PaymentStatusCompleted).
		Where("o.is_void = false AND o.is_refund = false").
		Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo).
		Group("p.id, p.name, c.id, c.name").
		Order("gross_profit DESC").
		Limit(20)

	if outletID != nil {
		productQuery = productQuery.Where("o.outlet_id = ?", *outletID)
	}

	err = productQuery.Scan(&productData).Error
	if err != nil {
		return nil, err
	}

	return &entities.ProfitLossAnalytics{
		Summary:     summary,
		Data:        data,
		ProductData: productData,
	}, nil
}
