package mysql

import (
	"context"
	"errors"
	"strings"
	"time"

	"gorm.io/gorm"

	"wucher/internal/domain/federal_state"
	"wucher/internal/shared/pkg/apperrors"
	"wucher/internal/shared/pkg/sortkey"
)

type FederalStateRepository struct {
	db     *gorm.DB
	schema federalStateSchema
}

func NewFederalStateRepository(db *gorm.DB) *FederalStateRepository {
	probe := newSchemaCache(db)
	schema := federalStateSchema{}

	schema.hasFederalStatesTable = probe.HasTable("federal_states")
	schema.federalStatesHasName = schema.hasFederalStatesTable && probe.HasColumn("federal_states", "name")

	schema.hasInsurancePatientDataTable = probe.HasTable("insurance_patient_data")
	if schema.hasInsurancePatientDataTable {
		schema.insurancePatientDataHasFederalStateID = probe.HasColumn("insurance_patient_data", "federal_state_id")
		schema.insurancePatientDataHasState = probe.HasColumn("insurance_patient_data", "state")
		schema.insurancePatientDataHasDeletedAt = probe.HasColumn("insurance_patient_data", "deleted_at")
	}

	schema.hasHealthInsuranceCompaniesTable = probe.HasTable("health_insurance_companies")
	if schema.hasHealthInsuranceCompaniesTable {
		schema.healthInsuranceCompaniesHasFederalStateID = probe.HasColumn("health_insurance_companies", "federal_state_id")
		schema.healthInsuranceCompaniesHasState = probe.HasColumn("health_insurance_companies", "state")
		schema.healthInsuranceCompaniesHasDeletedAt = probe.HasColumn("health_insurance_companies", "deleted_at")
	}

	schema.hasIcaosTable = probe.HasTable("icaos")
	if schema.hasIcaosTable {
		schema.icaosHasFederalStateID = probe.HasColumn("icaos", "federal_state_id")
		schema.icaosHasDeletedAt = probe.HasColumn("icaos", "deleted_at")
	}

	return &FederalStateRepository{db: db, schema: schema}
}

func (r *FederalStateRepository) Create(ctx context.Context, row *federal_state.FederalState) error {
	requestedIsActive := row.IsActive
	db := r.db.WithContext(ctx)
	if err := db.Create(row).Error; err != nil {
		return err
	}
	return db.Model(&federal_state.FederalState{}).Where("id = ?", row.ID).UpdateColumn("is_active", requestedIsActive).Error
}

func (r *FederalStateRepository) Update(ctx context.Context, row *federal_state.FederalState) error {
	return r.db.WithContext(ctx).Save(row).Error
}

func (r *FederalStateRepository) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
	if err := ensureNoReferenceBeforeDelete(ctx, r.db, "federal_states", id); err != nil {
		return err
	}
	if err := r.ensureNoFederalStateUsage(ctx, id); err != nil {
		return err
	}
	now := time.Now().UTC()
	updates := map[string]any{
		"deleted_at": now,
		"deleted_by": deletedBy,
		"updated_by": deletedBy,
	}
	return mapDeleteConstraintError(r.db.WithContext(ctx).
		Model(&federal_state.FederalState{}).
		Where("id = ? AND deleted_at IS NULL", id).
		Updates(updates).Error)
}

func (r *FederalStateRepository) ensureNoFederalStateUsage(ctx context.Context, id []byte) error {
	if len(id) != 16 {
		return nil
	}
	related := make([]string, 0, 3)
	db := r.db.WithContext(ctx)
	stateName := ""
	if r.schema.hasFederalStatesTable && r.schema.federalStatesHasName {
		_ = db.Table("federal_states").Select("name").Where("id = ?", id).Limit(1).Scan(&stateName).Error
	}
	stateName = strings.TrimSpace(stateName)

	if r.schema.hasInsurancePatientDataTable {
		conditions := make([]string, 0, 2)
		args := make([]any, 0, 2)
		if r.schema.insurancePatientDataHasFederalStateID {
			conditions = append(conditions, "federal_state_id = ?")
			args = append(args, id)
		}
		if stateName != "" && r.schema.insurancePatientDataHasState {
			conditions = append(conditions, "LOWER(TRIM(state)) = LOWER(TRIM(?))")
			args = append(args, stateName)
		}
		if len(conditions) == 0 {
			goto checkHealthInsuranceCompanies
		}
		query := db.Table("insurance_patient_data").Where("("+strings.Join(conditions, " OR ")+")", args...)
		if r.schema.insurancePatientDataHasDeletedAt {
			query = query.Where("deleted_at IS NULL")
		}
		var count int64
		if err := query.Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			related = append(related, "insurance patient data")
		}
	}

checkHealthInsuranceCompanies:
	if r.schema.hasHealthInsuranceCompaniesTable {
		conditions := make([]string, 0, 2)
		args := make([]any, 0, 2)
		if r.schema.healthInsuranceCompaniesHasFederalStateID {
			conditions = append(conditions, "federal_state_id = ?")
			args = append(args, id)
		}
		if stateName != "" && r.schema.healthInsuranceCompaniesHasState {
			conditions = append(conditions, "LOWER(TRIM(state)) = LOWER(TRIM(?))")
			args = append(args, stateName)
		}
		if len(conditions) == 0 {
			goto checkICAOs
		}
		query := db.Table("health_insurance_companies").Where("("+strings.Join(conditions, " OR ")+")", args...)
		if r.schema.healthInsuranceCompaniesHasDeletedAt {
			query = query.Where("deleted_at IS NULL")
		}
		var count int64
		if err := query.Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			related = append(related, "health insurance companies")
		}
	}

checkICAOs:
	if r.schema.hasIcaosTable {
		if !r.schema.icaosHasFederalStateID {
			goto finalize
		}
		var count int64
		query := db.Table("icaos").Where("federal_state_id = ?", id)
		if r.schema.icaosHasDeletedAt {
			query = query.Where("deleted_at IS NULL")
		}
		if err := query.Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			related = append(related, "icaos")
		}
	}

finalize:
	if len(related) > 0 {
		return apperrors.NewDeleteConflictError(related...)
	}
	return nil
}

type federalStateSchema struct {
	hasFederalStatesTable                     bool
	federalStatesHasName                      bool
	hasInsurancePatientDataTable              bool
	insurancePatientDataHasFederalStateID     bool
	insurancePatientDataHasState              bool
	insurancePatientDataHasDeletedAt          bool
	hasHealthInsuranceCompaniesTable          bool
	healthInsuranceCompaniesHasFederalStateID bool
	healthInsuranceCompaniesHasState          bool
	healthInsuranceCompaniesHasDeletedAt      bool
	hasIcaosTable                             bool
	icaosHasFederalStateID                    bool
	icaosHasDeletedAt                         bool
}

func (r *FederalStateRepository) GetByID(ctx context.Context, id []byte) (*federal_state.FederalState, error) {
	var row federal_state.FederalState
	err := r.db.WithContext(ctx).
		Preload("Land", "deleted_at IS NULL").
		Where("id = ? AND deleted_at IS NULL", id).
		First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &row, err
}

func (r *FederalStateRepository) List(ctx context.Context, filter, sort string, limit, offset int, landID []byte, landName string) ([]federal_state.FederalState, int64, error) {
	var rows []federal_state.FederalState
	var total int64

	base := r.db.WithContext(ctx).Model(&federal_state.FederalState{}).Where("federal_states.deleted_at IS NULL")
	if filter != "" {
		like := "%" + filter + "%"
		base = base.Where("federal_states.name LIKE ?", like)
	}
	if len(landID) > 0 {
		base = base.Where("federal_states.land_id = ?", landID)
	}
	if trimmedLandName := strings.TrimSpace(landName); trimmedLandName != "" {
		likeLandName := "%" + trimmedLandName + "%"
		base = base.Joins("JOIN lands ON lands.id = federal_states.land_id AND lands.deleted_at IS NULL").
			Where("lands.name LIKE ?", likeLandName)
	}

	query := base
	if sort != "" {
		query = query.Order(sort)
	} else {
		for _, clause := range sortkey.ActivePositiveSortClauses("federal_states", "is_active", "sortkey", "name", false) {
			query = query.Order(clause)
		}
	}
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	query = query.Preload("Land", "deleted_at IS NULL")
	if err := query.Find(&rows).Error; err != nil {
		return nil, 0, err
	}

	return rows, total, nil
}
